diff options
Diffstat (limited to 'sound/soc')
33 files changed, 13980 insertions, 2 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 35e662d270e..e2054fa16e4 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -13,7 +13,7 @@ menuconfig SND_SOC If you want ASoC support, you should say Y here and also to the specific driver for your SoC platform below. - + ASoC provides power efficient ALSA support for embedded battery powered SoC based systems like PDA's, Phones and Personal Media Players. @@ -45,6 +45,7 @@ source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/ux500/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9ea8ac827ad..c5d3966cf03 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += ux500/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7c205e77d83..efc806a205e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -105,7 +105,12 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS - help + select SND_SOC_AB3550 + select SND_SOC_AB5500 + select SND_SOC_AB8500 + select SND_SOC_CG29XX + select SND_SOC_AV8100 + help Normally ASoC codec drivers are only built if a machine driver which uses them is also built since they are only usable with a machine driver. Selecting this option will allow these drivers to be built @@ -421,6 +426,21 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate +config SND_SOC_AB3550 + tristate + +config SND_SOC_AB5500 + tristate + +config SND_SOC_AB8500 + tristate + +config SND_SOC_CG29XX + tristate + +config SND_SOC_AV8100 + tristate + # Amp config SND_SOC_LM4857 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index de8078178f8..c8b8fd3f8c5 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,7 @@ snd-soc-88pm860x-objs := 88pm860x-codec.o +snd-soc-ab3550-objs := ab3550.o +snd-soc-ab5500-objs := ab5500.o +snd-soc-ab8500_audio-objs := ab8500_audio.o snd-soc-ac97-objs := ac97.o snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o @@ -13,6 +16,8 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-ak4641-objs := ak4641.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o +snd-soc-av8100_audio-objs := av8100_audio.o +snd-soc-cg29xx-objs := cg29xx.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l73-objs := cs42l73.o @@ -100,6 +105,9 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o +obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-ab3550.o +obj-$(CONFIG_SND_SOC_AB5500) += snd-soc-ab5500.o +obj-$(CONFIG_SND_SOC_AB8500) += snd-soc-ab8500_audio.o obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o @@ -117,6 +125,8 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o +obj-$(CONFIG_SND_SOC_AV8100) += snd-soc-av8100_audio.o +obj-$(CONFIG_SND_SOC_CG29XX) += snd-soc-cg29xx.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o @@ -201,3 +211,9 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o +ifdef CONFIG_SND_SOC_UX500_DEBUG +CFLAGS_av8100_audio.o := -DDEBUG +CFLAGS_ab3550.o := -DDEBUG +CFLAGS_cg29xx.o := -DDEBUG +CFLAGS_ab8500_audio.o := -DDEBUG +endif diff --git a/sound/soc/codecs/ab3550.c b/sound/soc/codecs/ab3550.c new file mode 100644 index 00000000000..4a15ab64d32 --- /dev/null +++ b/sound/soc/codecs/ab3550.c @@ -0,0 +1,1429 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com>, + * Ola Lilja <ola.o.lilja@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 <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 <linux/mfd/abx500.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <asm/atomic.h> +#include <linux/rwsem.h> +#include <linux/mutex.h> +#include <stdarg.h> +#include "ab3550.h" + + +#define I2C_BANK 0 + +/* codec private data */ +struct ab3550_codec_dai_data { +}; + +static struct device *ab3550_dev; + +static u8 virtual_regs[] = { + 0, 0 +}; + +static void set_reg(u8 reg, u8 val) +{ + if (!ab3550_dev) { + pr_err("%s: The AB3550 codec driver not initialized.\n", + __func__); + return; + } + if (reg < AB3550_FIRST_REG) + return; + else if (reg <= AB3550_LAST_REG) { + abx500_set_register_interruptible( + ab3550_dev, I2C_BANK, reg, val); + } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs)) { + virtual_regs[reg - AB3550_LAST_REG - 1] = val; + } +} + +static void mask_set_reg(u8 reg, u8 mask, u8 val) +{ + if (!ab3550_dev) { + pr_err("%s: The AB3550 codec driver not initialized.\n", + __func__); + return; + } + if (reg < AB3550_FIRST_REG) + return; + else if (reg <= AB3550_LAST_REG) { + abx500_mask_and_set_register_interruptible( + ab3550_dev, I2C_BANK, reg, mask, val); + } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs)) { + virtual_regs[reg - AB3550_LAST_REG - 1] &= ~mask; + virtual_regs[reg - AB3550_LAST_REG - 1] |= val & mask; + } +} + +static u8 read_reg(u8 reg) +{ + if (!ab3550_dev) { + pr_err("%s: The AB3550 codec driver not initialized.\n", + __func__); + return 0; + } + if (reg < AB3550_FIRST_REG) + return 0; + else if (reg <= AB3550_LAST_REG) { + u8 val; + abx500_get_register_interruptible( + ab3550_dev, I2C_BANK, reg, &val); + return val; + } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs)) + return virtual_regs[reg - AB3550_LAST_REG - 1]; + dev_warn(ab3550_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_spkr, + 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 +}; + +/* This is only meant for debugging */ +static const char *widget_names[] = { + "EAR", "AUXO1", "AUXO2", "SPKR", "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" +}; + +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, .shift = EAR_PWR_SHIFT}, + {.widget = widget_auxo1, .reg = AUXO1, .shift = AUXOx_PWR_SHIFT}, + {.widget = widget_auxo2, .reg = AUXO2, .shift = AUXOx_PWR_SHIFT}, + {.widget = widget_spkr, .reg = SPKR, .shift = SPKR_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 = MBIAS_PWR_SHIFT}, + {.widget = widget_micbias2, .reg = MIC_BIAS2, + .shift = MBIAS_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 = AB3550_VIRTUAL_REG1, + .shift = IF0_DLD_L_PW_SHIFT}, + {.widget = widget_if0_dld_r, .reg = AB3550_VIRTUAL_REG1, + .shift = IF0_DLD_R_PW_SHIFT}, + {.widget = widget_if0_uld_l, .reg = AB3550_VIRTUAL_REG1, + .shift = IF0_ULD_L_PW_SHIFT}, + {.widget = widget_if0_uld_r, .reg = AB3550_VIRTUAL_REG1, + .shift = IF0_ULD_R_PW_SHIFT}, + + {.widget = widget_if1_dld_l, .reg = AB3550_VIRTUAL_REG1, + .shift = IF1_DLD_L_PW_SHIFT}, + {.widget = widget_if1_dld_r, .reg = AB3550_VIRTUAL_REG1, + .shift = IF1_DLD_R_PW_SHIFT}, + {.widget = widget_if1_uld_l, .reg = AB3550_VIRTUAL_REG1, + .shift = IF1_ULD_L_PW_SHIFT}, + {.widget = widget_if1_uld_r, .reg = AB3550_VIRTUAL_REG1, + .shift = IF1_ULD_R_PW_SHIFT}, + + {.widget = widget_mic1p1, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC1P1_PW_SHIFT}, + {.widget = widget_mic1n1, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC1N1_PW_SHIFT}, + {.widget = widget_mic1p2, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC1P2_PW_SHIFT}, + {.widget = widget_mic1n2, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC1N2_PW_SHIFT}, + + {.widget = widget_mic2p1, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC2P1_PW_SHIFT}, + {.widget = widget_mic2n1, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC2N1_PW_SHIFT}, + {.widget = widget_mic2p2, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC2P2_PW_SHIFT}, + {.widget = widget_mic2n2, .reg = AB3550_VIRTUAL_REG2, + .shift = MIC2N2_PW_SHIFT}, + + {.widget = widget_clock, .reg = CLOCK, .shift = CLOCK_ENABLE_SHIFT}, +}; + +DEFINE_MUTEX(ab3550_pm_mutex); + +static struct { + enum enum_widget stack[number_of_widgets]; + int p; +} pm_stack; + +struct ab3550_dai_private { + unsigned int fmt; +}; + +#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 ab3550_dapm_widgets[] = { +}; + +static const struct snd_soc_dapm_route intercon[] = { +}; + + +static const char *enum_rx2_select[] = {"I2S0", "I2S1"}; +static const char *enum_i2s_input_select[] = { + "tri-state", "MIC1", "MIC2", "mute" +}; +static const char *enum_apga1_source[] = {"LINEIN1", "MIC1", "MIC2"}; +static const char *enum_apga2_source[] = {"LINEIN2", "MIC1", "MIC2"}; +static const char *enum_dac_side_tone[] = {"TX1", "TX2"}; +static const char *enum_dac_power_mode[] = {"100%", "75%", "55%"}; +static const char *enum_ear_power_mode[] = {"100%", "70%"}; +static const char *enum_auxo_power_mode[] = { + "100%", "67%", "50%", "25%", "auto" +}; +static const char *enum_onoff[] = {"Off", "On"}; +static const char *enum_mbias_hiz_option[] = {"GND", "HiZ"}; +static const char *enum_mbias2_output_voltage[] = {"2.0v", "2.2v"}; +static const char *enum_mic_input_impedance[] = { + "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_signal_inversion[] = {"normal", "inverted"}; + +/* RX2 Select */ +static struct soc_enum soc_enum_rx2_select = + SOC_ENUM_SINGLE(RX2, 4, ARRAY_SIZE(enum_rx2_select), enum_rx2_select); + +/* I2S0 Input Select */ +static struct soc_enum soc_enum_i2s0_input_select = + SOC_ENUM_DOUBLE(INTERFACE0_DATA, 0, 2, + ARRAY_SIZE(enum_i2s_input_select), + enum_i2s_input_select); +/* I2S1 Input Select */ +static struct soc_enum soc_enum_i2s1_input_select = + SOC_ENUM_DOUBLE(INTERFACE1_DATA, 0, 2, + ARRAY_SIZE(enum_i2s_input_select), + enum_i2s_input_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); + +/* DAC1 Side Tone */ +static struct soc_enum soc_enum_dac1_side_tone = + SOC_ENUM_SINGLE(SIDETONE1_PGA, STx_MUX_SHIFT, + ARRAY_SIZE(enum_dac_side_tone), enum_dac_side_tone); + +/* DAC2 Side Tone */ +static struct soc_enum soc_enum_dac2_side_tone = + SOC_ENUM_SINGLE(SIDETONE2_PGA, STx_MUX_SHIFT, + ARRAY_SIZE(enum_dac_side_tone), enum_dac_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, EAR_PWR_MODE_SHIFT, + ARRAY_SIZE(enum_ear_power_mode), enum_ear_power_mode); + +/* AUXO Power Mode */ +static struct soc_enum soc_enum_auxo_power_mode = + SOC_ENUM_SINGLE(AUXO_PWR_MODE, AUXO_PWR_MODE_SHIFT, + ARRAY_SIZE(enum_auxo_power_mode), + enum_auxo_power_mode); + +/* MBIAS1 HiZ Option */ +static struct soc_enum soc_enum_mbias1_hiz_option = + SOC_ENUM_SINGLE(MIC_BIAS1, MBIAS_PDN_IMP_SHIFT, + ARRAY_SIZE(enum_mbias_hiz_option), + enum_mbias_hiz_option); + +/* MBIAS1 HiZ Option */ +static struct soc_enum soc_enum_mbias2_hiz_option = + SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS_PDN_IMP_SHIFT, + ARRAY_SIZE(enum_mbias_hiz_option), + enum_mbias_hiz_option); + +/* MBIAS2 Output voltage */ +static struct soc_enum soc_enum_mbias2_output_voltage = + SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS2_OUT_V_SHIFT, + ARRAY_SIZE(enum_mbias2_output_voltage), + enum_mbias2_output_voltage); + +static struct soc_enum soc_enum_mbias2_internal_resistor = + 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_input_impedance = + SOC_ENUM_SINGLE(MIC1_GAIN, MICx_IN_IMP_SHIFT, + ARRAY_SIZE(enum_mic_input_impedance), + enum_mic_input_impedance); + +static struct soc_enum soc_enum_mic2_input_impedance = + SOC_ENUM_SINGLE(MIC2_GAIN, MICx_IN_IMP_SHIFT, + ARRAY_SIZE(enum_mic_input_impedance), + enum_mic_input_impedance); + +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(SIDETONE1_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(SIDETONE2_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(AUXO1, 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(AUXO1, AUXOx_PULLDOWN_SHIFT, + ARRAY_SIZE(enum_optional_resistor), + enum_optional_resistor); + +static struct snd_kcontrol_new ab3550_snd_controls[] = { + /* RX Routing */ + SOC_ENUM("RX2 Select", soc_enum_rx2_select), + SOC_SINGLE("LINE1 Adder", LINE1_ADDER, 0, 0x07, 0), + SOC_SINGLE("LINE2 Adder", LINE2_ADDER, 0, 0x07, 0), + SOC_SINGLE("EAR Adder", EAR_ADDER, 0, 0x07, 0), + SOC_SINGLE("SPKR Adder", SPKR_ADDER, 0, 0x07, 0), + SOC_SINGLE("AUXO1 Adder", AUXO1_ADDER, 0, 0x07, 0), + SOC_SINGLE("AUXO2 Adder", AUXO2_ADDER, 0, 0x07, 0), + /* TX Routing */ + SOC_SINGLE("MIC1 Input Select", MIC1_INPUT_SELECT, 0, 0xff, 0), + SOC_SINGLE("MIC2 Input Select", MIC1_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_SINGLE("APGA1 Destination", APGA1_ADDER, 0, 0x3f, 0), + SOC_SINGLE("APGA2 Destination", APGA2_ADDER, 0, 0x3f, 0), + 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_DIGITAL_PGA, 0, 0x43, 0), + SOC_SINGLE("RX-DPGA2 Gain", RX1_DIGITAL_PGA, 0, 0x43, 0), + SOC_SINGLE("RX-DPGA3 Gain", RX3_DIGITAL_PGA, 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("SPKR Gain", SPKR, SPKR_GAIN_SHIFT, 0x16, 0), + SOC_SINGLE("EAR Gain", EAR, EAR_GAIN_SHIFT, 0x0e, 0), + SOC_SINGLE("AUXO1 Gain", AUXO1, AUXOx_GAIN_SHIFT, 0x0c, 0), + SOC_SINGLE("AUXO2 Gain", AUXO2, 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_DIGITAL_PGA1, TXDPGAx_SHIFT, 0x0f, 0), + SOC_SINGLE("TX-DPGA2 Gain", TX_DIGITAL_PGA2, TXDPGAx_SHIFT, 0x0f, 0), + /* Volume Control of Side Tone and Analog Loop */ + SOC_SINGLE("ST-PGA1 Gain", SIDETONE1_PGA, STx_PGA_SHIFT, 0x0a, 0), + SOC_SINGLE("ST-PGA2 Gain", SIDETONE2_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("AUXO Power Mode", soc_enum_auxo_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("AUXO1 Pulldown Resistor", soc_enum_auxo1_pulldown_resistor), + SOC_ENUM("AUXO2 Pulldown Resistor", soc_enum_auxo2_pulldown_resistor), + /* TX Properties */ + SOC_SINGLE("MIC1 VMID", MIC1_VMID_SELECT, 0, 0xff, 0), + SOC_SINGLE("MIC2 VMID", MIC2_VMID_SELECT, 0, 0xff, 0), + SOC_ENUM("MBIAS1 HiZ Option", soc_enum_mbias1_hiz_option), + SOC_ENUM("MBIAS2 HiZ Option", soc_enum_mbias2_hiz_option), + SOC_ENUM("MBIAS2 Output Voltage", soc_enum_mbias2_output_voltage), + SOC_ENUM("MBIAS2 Internal Resistor", soc_enum_mbias2_internal_resistor), + SOC_ENUM("MIC1 Input Impedance", soc_enum_mic1_input_impedance), + SOC_ENUM("MIC2 Input Impedance", soc_enum_mic2_input_impedance), + 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 Loop", INTERFACE_LOOP, 0, 0x0f, 0), + SOC_SINGLE("Interface Swap", INTERFACE_SWAP, 0, 0x1f, 0), + /* Miscellaneous */ + SOC_SINGLE("Negative Charge Pump", NEGATIVE_CHARGE_PUMP, 0, 0x03, 0) +}; + +/* count the number of 1 */ +#define count_ones(x) ({ \ + int num; \ + for (num = 0; x; (x) &= (x) - 1, num++) \ + ; \ + num; \ + }) + +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; + dev_dbg(ab3550_dev, "%s: processing widget %s.\n", + __func__, widget_names[w]); + + 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); + dev_dbg(ab3550_dev, "%s: widget %s powered %s.\n", + __func__, widget_names[w], + onoff == POWER_ON ? "on" : "off"); + + 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(&ab3550_pm_mutex)) { + dev_warn(ab3550_dev, + "%s: Signal received while waiting on the PM mutex.\n", + __func__); + return; + } + power_widget_unlocked(onoff, widget); + mutex_unlock(&ab3550_pm_mutex); +} + +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_dbg(ab3550_dev, "%s from %s> 0x%02X : 0x%02X.\n", + __func__, where, reg, read_reg(reg)); + } while (1); + va_end(ap); +} + +/** + * update the link between 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(&ab3550_pm_mutex)) { + dev_warn(ab3550_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(&ab3550_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 SPKR_ADDER: + return widget_spkr; + case LINE1_ADDER: + return widget_line1; + case LINE2_ADDER: + return widget_line2; + case APGA1_ADDER: + return widget_apga1; + case APGA2_ADDER: + return widget_apga2; + default: + return number_of_widgets; + } +} + +static int ab3550_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(&codec->dapm, ab3550_dapm_widgets, + ARRAY_SIZE(ab3550_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&codec->dapm); + return 0; +} + +static void power_for_playback(enum enum_power onoff, int ifsel) +{ + dev_dbg(ab3550_dev, "%s: interface %d power %s.\n", __func__, + ifsel, onoff == POWER_ON ? "on" : "off"); + + if (mutex_lock_interruptible(&ab3550_pm_mutex)) { + dev_warn(ab3550_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(&ab3550_pm_mutex); +} + +static void power_for_capture(enum enum_power onoff, int ifsel) +{ + dev_dbg(ab3550_dev, "%s: interface %d power %s", __func__, + ifsel, onoff == POWER_ON ? "on" : "off"); + if (mutex_lock_interruptible(&ab3550_pm_mutex)) { + dev_warn(ab3550_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(&ab3550_pm_mutex); +} + +static int ab3550_add_controls(struct snd_soc_codec *codec) +{ + int err = 0, i, n = ARRAY_SIZE(ab3550_snd_controls); + + pr_debug("%s: %s called.\n", __FILE__, __func__); + for (i = 0; i < n; i++) { + err = snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &ab3550_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 ab3550_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 (!ab3550_dev) { + pr_err("%s: The AB3550 codec driver not initialized.\n", + __func__); + return -EAGAIN; + } + dev_info(ab3550_dev, "%s called.\n", __func__); + 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 ab3550_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(ab3550_dev, "%s: A %s stream is already active.\n", + __func__, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "PLAYBACK" : "CAPTURE"); + return -EBUSY; + } + return 0; +} +static int ab3550_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_info(ab3550_dev, "%s called.\n", __func__); + + /* Configure registers for either playback or capture */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + power_for_playback(POWER_ON, dai->id); + dump_registers(__func__, + dai->id == 0 ? INTERFACE0 : INTERFACE1, + RX1, RX2, SPKR, EAR, -1); + } else { + power_for_capture(POWER_ON, dai->id); + dump_registers(__func__, MIC_BIAS1, MIC_BIAS2, MIC1_GAIN, TX1, + dai->id == 0 ? INTERFACE0 : INTERFACE1, -1); + } + return 0; +} + +static void ab3550_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 iface = dai->id == 0 ? INTERFACE0 : INTERFACE1; + dev_info(ab3550_dev, "%s called.\n", __func__); + 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 ab3550_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + return 0; +} + +static int ab3550_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + u8 iface = (codec_dai->id == 0) ? INTERFACE0 : INTERFACE1; + u8 val = 0; + dev_info(ab3550_dev, "%s called.\n", __func__); + + 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(ab3550_dev, "AB3550_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(ab3550_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 ab3550_dai_drv[] = { + { + .name = "ab3550-codec-dai.0", + .id = 0, + .playback = { + .stream_name = "AB3550.0 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "AB3550.0 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .startup = ab3550_pcm_startup, + .prepare = ab3550_pcm_prepare, + .hw_params = ab3550_pcm_hw_params, + .shutdown = ab3550_pcm_shutdown, + .set_sysclk = ab3550_set_dai_sysclk, + .set_fmt = ab3550_set_dai_fmt, + } + }, + .symmetric_rates = 1, + }, + { + .name = "ab3550-codec-dai.1", + .id = 1, + .playback = { + .stream_name = "AB3550.1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "AB3550.0 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .startup = ab3550_pcm_startup, + .prepare = ab3550_pcm_prepare, + .hw_params = ab3550_pcm_hw_params, + .shutdown = ab3550_pcm_shutdown, + .set_sysclk = ab3550_set_dai_sysclk, + .set_fmt = ab3550_set_dai_fmt, + } + }, + .symmetric_rates = 1, + } +}; +EXPORT_SYMBOL_GPL(ab3550_dai_drv); + +static int ab3550_codec_probe(struct snd_soc_codec *codec) +{ + int ret; + + pr_info("%s: Enter.\n", __func__); + + /* Add controls */ + if (ab3550_add_controls(codec) < 0) + return ret; + + /* Add widgets */ + ab3550_add_widgets(codec); + + return 0; +} + +static int ab3550_codec_remove(struct snd_soc_codec *codec) +{ + snd_soc_dapm_free(&codec->dapm); + + return 0; +} + +#ifdef CONFIG_PM +static int ab3550_codec_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0); + + return 0; +} + +static int ab3550_codec_resume(struct snd_soc_codec *codec) +{ + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0xff); + + return 0; +} +#else +#define ab3550_codec_resume NULL +#define ab3550_codec_suspend NULL +#endif + +/* + * This function is only called by the SOC framework to + * set registers associated to the mixer controls. + */ +static int ab3550_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 APGA1_ADDER: + case APGA2_ADDER: { + int i; + enum enum_widget apga; + enum enum_widget apga_dst[] = { + widget_auxo2, widget_auxo1, widget_ear, widget_spkr, + widget_line2, widget_line1 + }; + + apga = adder_sink_translate(reg); + 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, apga, apga_dst[i], + reg, 1 << i, value); + } + break; + } + + case EAR_ADDER: + case AUXO1_ADDER: + case AUXO2_ADDER: + case SPKR_ADDER: + case LINE1_ADDER: + case LINE2_ADDER: { + int i; + enum enum_widget widgets[] = { + widget_dac1, widget_dac2, widget_dac3, + }; + 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; + } + + default: + set_reg(reg, value); + } + return 0; +} + +static unsigned int ab3550_codec_read_reg(struct snd_soc_codec *codec, + unsigned int reg) +{ + return read_reg(reg); +} + +static struct snd_soc_codec_driver ab3550_codec_drv = { + .probe = ab3550_codec_probe, + .remove = ab3550_codec_remove, + .suspend = ab3550_codec_suspend, + .resume = ab3550_codec_resume, + .read = ab3550_codec_read_reg, + .write = ab3550_codec_write_reg, +}; +EXPORT_SYMBOL_GPL(ab3550_codec_drv); + +static inline void init_playback_route(void) +{ + update_widgets_link(LINK, widget_if0_dld_l, widget_rx1, 0, 0, 0); + update_widgets_link(LINK, widget_rx1, widget_dac1, 0, 0, 0); + update_widgets_link(LINK, widget_dac1, widget_spkr, + SPKR_ADDER, DAC1_TO_ADDER_MASK, 0xff); + + update_widgets_link(LINK, widget_if0_dld_r, widget_rx2, + RX2, RX2_IF_SELECT_MASK, 0); + update_widgets_link(LINK, widget_rx2, widget_dac2, 0, 0, 0); + update_widgets_link(LINK, widget_dac2, widget_ear, + EAR_ADDER, DAC2_TO_ADDER_MASK, 0xff); +} + +static inline void init_capture_route(void) +{ + update_widgets_link(LINK, widget_micbias2, widget_mic1p1, + 0, 0, 0); + update_widgets_link(LINK, widget_micbias2, widget_mic1n1, + 0, 0, 0); + update_widgets_link(LINK, widget_mic1p1, widget_mic1, + MIC1_INPUT_SELECT, MICxP1_SEL_MASK, 0xff); + update_widgets_link(LINK, widget_mic1n1, widget_mic1, + MIC1_INPUT_SELECT, MICxN1_SEL_MASK, 0xff); + update_widgets_link(LINK, widget_mic1, widget_adc1, + 0, 0, 0); + update_widgets_link(LINK, widget_adc1, widget_tx1, + 0, 0, 0); + update_widgets_link(LINK, widget_tx1, widget_if0_uld_l, + INTERFACE0_DATA, I2Sx_L_DATA_MASK, + I2Sx_L_DATA_TX1_MASK); + update_widgets_link(LINK, widget_tx1, widget_if0_uld_r, + INTERFACE0_DATA, I2Sx_R_DATA_MASK, + I2Sx_R_DATA_TX1_MASK); +} + +static inline void init_playback_gain(void) +{ + mask_set_reg(RX1_DIGITAL_PGA, RXx_PGA_GAIN_MASK, + 0x40 << RXx_PGA_GAIN_SHIFT); + mask_set_reg(RX2_DIGITAL_PGA, RXx_PGA_GAIN_MASK, + 0x40 << RXx_PGA_GAIN_SHIFT); + mask_set_reg(EAR, EAR_GAIN_MASK, 0x06 << EAR_GAIN_SHIFT); + mask_set_reg(SPKR, SPKR_GAIN_MASK, 0x6 << SPKR_GAIN_SHIFT); +} + +static inline void init_capture_gain(void) +{ + mask_set_reg(MIC1_GAIN, MICx_GAIN_MASK, 0x06 << MICx_GAIN_SHIFT); + mask_set_reg(TX_DIGITAL_PGA1, TXDPGAx_MASK, 0x0f << TXDPGAx_SHIFT); +} + +static __devinit int ab3550_codec_drv_probe(struct platform_device *pdev) +{ + struct ab3550_codec_dai_data *codec_drvdata; + int ret = 0; + u8 reg; + + pr_debug("%s: Enter.\n", __func__); + + pr_info("%s: Init codec private data.\n", __func__); + codec_drvdata = kzalloc(sizeof(struct ab3550_codec_dai_data), GFP_KERNEL); + if (codec_drvdata == NULL) + return -ENOMEM; + + /* TODO: Add private data to codec_drvdata */ + + platform_set_drvdata(pdev, codec_drvdata); + + pr_info("%s: Register codec.\n", __func__); + ret = snd_soc_register_codec(&pdev->dev, &ab3550_codec_drv, &ab3550_dai_drv[0], 2); + if (ret < 0) { + pr_debug("%s: Error: Failed to register codec (ret = %d).\n", + __func__, + ret); + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + return ret; + } + + ab3550_dev = &pdev->dev; + /* Initialize the codec registers */ + for (reg = AB3550_FIRST_REG; reg <= AB3550_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 0; +} + +static int __devexit ab3550_codec_drv_remove(struct platform_device *pdev) +{ + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0); + + ab3550_dev = NULL; + + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + + return 0; +} + +static int ab3550_codec_drv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int ab3550_codec_drv_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ab3550_codec_platform_drv = { + .driver = { + .name = "ab3550-codec", + .owner = THIS_MODULE, + }, + .probe = ab3550_codec_drv_probe, + .remove = __devexit_p(ab3550_codec_drv_remove), + .suspend = ab3550_codec_drv_suspend, + .resume = ab3550_codec_drv_resume, +}; + + +static int __devinit ab3550_codec_platform_drv_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + ab3550_dev = NULL; + + ret = platform_driver_register(&ab3550_codec_platform_drv); + if (ret != 0) + pr_err("Failed to register AB3550 platform driver (%d)!\n", ret); + + return ret; +} + +static void __exit ab3550_codec_platform_drv_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + platform_driver_unregister(&ab3550_codec_platform_drv); +} + + +module_init(ab3550_codec_platform_drv_init); +module_exit(ab3550_codec_platform_drv_exit); + +MODULE_DESCRIPTION("AB3550 Codec driver"); +MODULE_AUTHOR("Xie Xiaolei <xie.xiaolei@stericsson.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ab3550.h b/sound/soc/codecs/ab3550.h new file mode 100644 index 00000000000..fe9c77b1a62 --- /dev/null +++ b/sound/soc/codecs/ab3550.h @@ -0,0 +1,333 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Xie Xiaolei <xie.xiaolei@etericsson.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. + */ +#ifndef AB3550_CODEC_REGISTERS_H +#define AB3550_CODEC_REGISTERS_H + +#define AB3550_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define AB3550_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* MIC BIAS */ + +#define MIC_BIAS1 0X31 +#define MIC_BIAS2 0X32 +#define MBIAS2_OUT_V_MASK 0x04 +#define MBIAS2_OUT_V_SHIFT 2 +#define MBIAS_PWR_MASK 0x02 +#define MBIAS_PWR_SHIFT 1 +#define MBIAS_PDN_IMP_MASK 0x01 +#define MBIAS_PDN_IMP_SHIFT 0 + +#define MIC_BIAS2_VAD 0x33 +#define MBIAS2_R_INT_MASK 0x01 +#define MBIAS2_R_INT_SHIFT 0 + +/* MIC */ +#define MIC1_GAIN 0x34 +#define MIC2_GAIN 0x35 +#define MICx_GAIN_MASK 0xF0 +#define MICx_GAIN_SHIFT 4 +#define MICx_IN_IMP_MASK 0x0C +#define MICx_IN_IMP_SHIFT 2 +#define MICx_PWR_MASK 0x01 +#define MICx_PWR_SHIFT 0 + +#define MIC1_INPUT_SELECT 0x36 +#define MIC2_INPUT_SELECT 0x37 +#define MICxP1_SEL_MASK 0x80 +#define MICxP1_SEL_SHIFT 7 +#define MICxN1_SEL_MASK 0x40 +#define MICxN1_SEL_SHIFT 6 +#define MICxP2_SEL_MASK 0x20 +#define MICxP2_SEL_SHIFT 5 +#define MICxN2_SEL_MASK 0x10 +#define MICxN2_SEL_SHIFT 4 +#define LINEIN_SEL_MASK 0x03 +#define LINEIN_SEL_SHIFT 0 + +#define MIC1_VMID_SELECT 0x38 +#define MIC2_VMID_SELECT 0x39 +#define VMIDx_ENABLE_MASK 0xC0 +#define VMIDx_ENABLE_SHIFT 6 +#define VMIDx_LINEIN1_N_MASK 0x20 +#define VMIDx_LINEIN1_N_SHIFT 5 +#define VMIDx_LINEIN2_N_MASK 0x10 +#define VMIDx_LINEIN2_N_SHIFT 4 +#define VMIDx_MICxP1_MASK 0x08 +#define VMIDx_MICxP1_SHIFT 3 +#define VMIDx_MICxP2_MASK 0x04 +#define VMIDx_MICxP2_SHIFT 2 +#define VMIDx_MICxN1_MASK 0x02 +#define VMIDx_MICxN1_SHIFT 1 +#define VMIDx_MICxN2_MASK 0x01 +#define VMIDx_MICxN2_SHIFT 0 + +#define MIC2_TO_MIC1 0x3A +#define MIC2_TO_MIC1_MASK 0x03 +#define MIC2_TO_MIC1_SHIFT 0 + +/* Analog Loop */ +#define ANALOG_LOOP_PGA1 0x3B +#define ANALOG_LOOP_PGA2 0x3C +#define APGAx_GAIN_MASK 0xF8 +#define APGAx_GAIN_SHIFT 3 +#define APGAx_PWR_MASK 0x04 +#define APGAx_PWR_SHIFT 2 +#define APGAx_MUX_MASK 0x03 +#define APGAx_MUX_SHIFT 0 +#define APGAx_MUX_MIC1_MASK 0x01 +#define APGAx_MUX_MIC1_SHIFT 0 +#define APGAx_MUX_MIC2_MASK 0x02 +#define APGAx_MUX_MIC2_SHIFT 1 + + +#define APGA_VMID_SELECT 0x3D +#define VMID_APGA1_ENABLE_MASK 0xC0 +#define VMID_APGA1_ENABLE_SHIFT 6 +#define VMID_APGA1_LINEIN1_MASK 0x20 +#define VMID_APGA1_LINEIN1_SHIFT 5 +#define VMID_APGA2_ENABLE_MASK 0x0C +#define VMID_APGA2_ENABLE_SHIFT 2 +#define VMID_APGA2_LINEIN2_MASK 0x02 +#define VMID_APGA2_LINEIN2_SHIFT 1 + +/* Output Amplifiers */ +#define EAR 0x3E +#define EAR_PWR_MODE_MASK 0x20 +#define EAR_PWR_MODE_SHIFT 5 +#define EAR_PWR_MASK 0x10 +#define EAR_PWR_SHIFT 4 +#define EAR_GAIN_MASK 0x0F +#define EAR_GAIN_SHIFT 0 + +#define AUXO1 0x3F +#define AUXO2 0x40 +#define AUXOx_PWR_MASK 0x80 +#define AUXOx_PWR_SHIFT 7 +#define AUXOx_INV_MASK 0x40 +#define AUXOx_INV_SHIFT 6 +#define AUXOx_PULLDOWN_MASK 0x20 +#define AUXOx_PULLDOWN_SHIFT 5 +#define AUXOx_GAIN_MASK 0x0F +#define AUXOx_GAIN_SHIFT 0 + +#define AUXO_PWR_MODE 0x41 +#define AUT_PWR_MODE_MASK 0x04 +#define AUT_PWR_MODE_SHIFT 2 +#define AUXO_PWR_MODE_MASK 0x03 +#define AUXO_PWR_MODE_SHIFT 0 + +#define OFFSET_CANCEL 0x42 +#define SPKR_OFF_CANC_MASK 0x04 +#define SPKR_OFF_CANC_SHIFT 2 +#define AUXO_OFF_CANC_MASK 0x02 +#define AUXO_OFF_CANC_SHIFT 1 +#define OFFSET_CLOCK_MASK 0x01 +#define OFFSET_CLOCK_SHIFT 0 + +#define SPKR 0x43 +#define OVR_CURR_PROT_MASK 0x80 +#define OVR_CURR_PROT_SHIFT 7 +#define SPKR_PWR_MASK 0x40 +#define SPKR_PWR_SHIFT 6 +#define SPKR_GAIN_MASK 0x1F +#define SPKR_GAIN_SHIFT 0 + +#define LINE1 0x44 +#define LINE2 0x45 +#define LINEx_PWR_MASK 0x80 +#define LINEx_PWR_SHIFT 7 +#define LINEx_INV_MASK 0x40 +#define LINEx_INV_SHIFT 6 +#define VMID_BUFFx_MASK 0x10 +#define VMID_BUFFx_SHIFT 4 +#define LINEx_GAIN_MASK 0x0F +#define LINEx_GAIN_SHIFT 0 + +/* Analog loop Routing */ + +#define APGA1_ADDER 0x46 +#define APGA2_ADDER 0x47 +#define APGAx_TO_LINE1_MASK 0x20 +#define APGAx_TO_LINE1_SHIFT 0x5F +#define APGAx_TO_LINE2_MASK 0x10 +#define APGAx_TO_LINE2_SHIFT 4 +#define APGAx_TO_SPKR_MASK 0x08 +#define APGAx_TO_SPKR_SHIFT 3 +#define APGAx_TO_EAR_MASK 0x04 +#define APGAx_TO_EAR_SHIFT 2 +#define APGAx_TO_AUXO1_MASK 0x02 +#define APGAx_TO_AUXO1_SHIFT 1 +#define APGAx_TO_AUXO2_MASK 0x01 +#define APGAx_TO_AUXO2_SHIFT 0 +#define APGAx_ADDER_VALID_BITS_MASK 0x3F + +/* Output Amplifiers Routing */ + +#define EAR_ADDER 0x48 +#define AUXO1_ADDER 0x49 +#define AUXO2_ADDER 0x4A +#define SPKR_ADDER 0x4B +#define LINE1_ADDER 0x4C +#define LINE2_ADDER 0x4D +#define DAC3_TO_ADDER_MASK 0x04 +#define DAC3_TO_ADDER_SHIFT 2 +#define DAC2_TO_ADDER_MASK 0x02 +#define DAC2_TO_ADDER_SHIFT 1 +#define DAC1_TO_ADDER_MASK 0x01 +#define DAC1_TO_ADDER_SHIFT 0 + +#define EAR_TO_MIC2 0x4E +#define EAR_TO_MIC2_MASK 0x01 +#define EAR_TO_MIC2_SHIFT 0 + +#define SPKR_TO_MIC2 0x4F +#define SPKR_TO_MIC2_MASK 0x01 +#define SPKR_TO_MIC2_SHIFT 0 + +#define NEGATIVE_CHARGE_PUMP 0x50 +#define NCP_MODE_MASK 0x02 +#define NCP_MODE_SHIFT 1 +#define NCP_PWR_MASK 0x01 +#define NCP_PWR_SHIFT 0 + +#define TX1 0x51 +#define TX2 0x52 +#define TXx_HP_FILTER_MASK 0x0C +#define TXx_HP_FILTER_SHIFT 2 +#define TXx_PWR_MASK 0x02 +#define TXx_PWR_SHIFT 1 +#define ADCx_PWR_MASK 0x01 +#define ADCx_PWR_SHIFT 0 + +#define RX1 0x53 +#define RX2 0x54 +#define RX2_IF_SELECT_MASK 0x10 +#define RX2_IF_SELECT_SHIFT 4 +#define RX3 0x55 +#define RXx_PWR_MASK 0x08 +#define RXx_PWR_SHIFT 3 +#define DACx_PWR_MASK 0x04 +#define DACx_PWR_SHIFT 2 +#define DACx_PWR_MODE_MASK 0x03 +#define DACx_PWR_MODE_SHIFT 0 + +#define TX_DIGITAL_PGA1 0X56 +#define TX_DIGITAL_PGA2 0X57 +#define TXDPGAx_MASK 0x0F +#define TXDPGAx_SHIFT 0 + +#define RX1_DIGITAL_PGA 0x58 +#define RX2_DIGITAL_PGA 0x59 +#define RX3_DIGITAL_PGA 0x5A +#define RXx_PGA_GAIN_MASK 0x7F +#define RXx_PGA_GAIN_SHIFT 0 + +#define SIDETONE1_PGA 0x5B +#define SIDETONE2_PGA 0x5C +#define STx_HP_FILTER_MASK 0x60 +#define STx_HP_FILTER_SHIFT 5 +#define STx_MUX_MASK 0x10 +#define STx_MUX_SHIFT 4 +#define STx_PGA_MASK 0x0F +#define STx_PGA_SHIFT 0 + +/* clock */ + +#define CLOCK 0x5D +#define CLOCK_REF_SELECT_MASK 0x02 +#define CLOCK_REF_SELECT_SHIFT 1 +#define CLOCK_ENABLE_MASK 0x01 +#define CLOCK_ENABLE_SHIFT 0 + +/* Interface */ + +#define INTERFACE0 0x5E +#define INTERFACE1 0x60 +#define I2Sx_WORDLENGTH_MASK 0x40 +#define I2Sx_WORDLENGTH_SHIFT 6 +#define MASTER_GENx_PWR_MASK 0x20 +#define MASTER_GENx_PWR_SHIFT 5 +#define I2Sx_MODE_MASK 0x10 +#define I2Sx_MODE_SHIFT 4 +#define I2Sx_TRISTATE_MASK 0x08 +#define I2Sx_TRISTATE_SHIFT 3 +#define I2Sx_PULLDOWN_MASK 0x04 +#define I2Sx_PULLDOWN_SHIFT 2 +#define I2Sx_SR_MASK 0x03 +#define I2Sx_SR_SHIFT 0 +#define I2Sx_SR_8000Hz 0 +#define I2Sx_SR_16000Hz 1 +#define I2Sx_SR_44100Hz 2 +#define I2Sx_SR_48000Hz 3 + +#define INTERFACE0_DATA 0x5F +#define INTERFACE1_DATA 0x61 +#define I2Sx_L_DATA_MASK 0x0C +#define I2Sx_L_DATA_TX1_MASK 0x04 +#define I2Sx_L_DATA_TX2_MASK 0x08 +#define I2Sx_L_DATA_SHIFT 2 +#define I2Sx_R_DATA_MASK 0x03 +#define I2Sx_R_DATA_TX1_MASK 0x01 +#define I2Sx_R_DATA_TX2_MASK 0x02 +#define I2Sx_R_DATA_SHIFT 0 + +#define INTERFACE_LOOP 0x62 +#define I2S0_INT_LOOP_MASK 0x08 +#define I2S0_INT_LOOP_SHIFT 3 +#define I2S0_EXT_LOOP_MASK 0x04 +#define I2S0_EXT_LOOP_SHIFT 2 +#define I2S1_INT_LOOP_MASK 0x02 +#define I2S1_INT_LOOP_SHIFT 1 +#define I2S1_EXT_LOOP_MASK 0x01 +#define I2S1_EXT_LOOP_SHIFT 0 + +#define INTERFACE_SWAP 0x63 +#define RX_SWAP0_MASK 0x10 +#define RX_SWAP0_SHIFT 4 +#define RX_SWAP1_MASK 0x08 +#define RX_SWAP1_SHIFT 3 +#define IF_SWAP_MASK 0x04 +#define IF_SWAP_SHIFT 2 +#define IO_SWAP0_MASK 0x02 +#define IO_SWAP0_SHIFT 1 +#define IO_SWAP1_MASK 0x01 +#define IO_SWAP1_SHIFT 0 + +#define AB3550_FIRST_REG MIC_BIAS1 +#define AB3550_LAST_REG INTERFACE_SWAP + +#define AB3550_VIRTUAL_REG1 (AB3550_LAST_REG + 1) +#define IF0_DLD_L_PW_SHIFT 0 +#define IF0_DLD_R_PW_SHIFT 1 +#define IF0_ULD_L_PW_SHIFT 2 +#define IF0_ULD_R_PW_SHIFT 3 +#define IF1_DLD_L_PW_SHIFT 4 +#define IF1_DLD_R_PW_SHIFT 5 +#define IF1_ULD_L_PW_SHIFT 6 +#define IF1_ULD_R_PW_SHIFT 7 + +#define AB3550_VIRTUAL_REG2 (AB3550_LAST_REG + 2) +#define MIC1P1_PW_SHIFT 0 +#define MIC1N1_PW_SHIFT 1 +#define MIC1P2_PW_SHIFT 2 +#define MIC1N2_PW_SHIFT 3 +#define MIC2P1_PW_SHIFT 4 +#define MIC2N1_PW_SHIFT 5 +#define MIC2P2_PW_SHIFT 6 +#define MIC2N2_PW_SHIFT 7 + + +#endif diff --git a/sound/soc/codecs/ab5500.c b/sound/soc/codecs/ab5500.c new file mode 100755 index 00000000000..aff0ad51f6a --- /dev/null +++ b/sound/soc/codecs/ab5500.c @@ -0,0 +1,1806 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * 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/regulator/consumer.h> +#include <linux/spi/spi.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 <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <asm/atomic.h> +#include <linux/rwsem.h> +#include <linux/mutex.h> +#include <stdarg.h> +#include "ab5500.h" + +/* No of digital interface on the Codec */ +#define NO_CODEC_DAI_IF 2 + +/* codec private data */ +struct ab5500_codec_dai_data { + bool playback_active; + bool capture_active; + +}; + +enum regulator_idx { + REGULATOR_DMIC, + REGULATOR_AMIC +}; + +static struct device *ab5500_dev; +static struct regulator_bulk_data reg_info[2] = { + { .supply = "vdigmic" }, + { .supply = "v-amic" } +}; + +static bool reg_enabled[2] = { + false, + false +}; + +static u8 virtual_regs[] = { + 0, 0, 0, 0, 0 +}; + +static int ab5500_clk_request; +static DEFINE_MUTEX(ab5500_clk_mutex); + +#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; + } + /* Check if the reg value falls within the + * range of AB5500 real registers. If + * so, set the mask */ + 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 */ + /*Compute the difference between the new value and the old value. + *1.If there is no difference, do nothing. + *2.If the difference is in the PWR_SHIFT, + *set the PWR masks appropriately. + */ + 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) { + /* + * If the new value has PWR_SHIFT + * disabled, set the + * PWR_MASK to 0 + */ + mask_set_reg(SPKR1, SPKRx_PWR_MASK, 0); + } + else { + /* Else, set the PWR_MASK values based on the old value. */ + 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) { + /* + * If the new value has PWR_SHIFT + * disabled, set the + * PWR_MASK to 0 + */ + mask_set_reg(SPKR2, SPKRx_PWR_MASK, 0); + } + else { + /* Else, set the PWR_MASK values based on the old value. */ + 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; + } + /* Check if the reg value falls within the range of AB5500 real + * registers.If so, set the mask */ + 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 +}; + +/* This is only meant for debugging */ +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" +}; + +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 = DUMMY_REG, .shift = 0}, + {.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 ab5500_codec_dai_data ab5500_codec_privates[NO_CODEC_DAI_IF] = { + { + .playback_active = false, + .capture_active = false, + }, + { + .playback_active = false, + .capture_active = false, + } +}; + +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[] = { + "SPKR1 power down", "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(SPKR1, SPKRx_PWR_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", RX2_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) +}; + +/* count the number of 1 */ +#define count_ones(x) ({ \ + int num; \ + typeof(x) y = x; \ + for (num = 0; y; y &= y - 1, num++) \ + ; \ + num; \ + }) + +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; + dev_dbg(ab5500_dev, "%s: processing widget %s.\n", + __func__, widget_names[w]); + + 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); + dev_dbg(ab5500_dev, "%s: widget %s powered %s.\n", + __func__, widget_names[w], + onoff == POWER_ON ? "on" : "off"); + 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); +} + + +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_dbg(ab5500_dev, "%s from %s> 0x%02X : 0x%02X.\n", + __func__, where, reg, read_reg(reg)); + } while (1); + va_end(ap); +} + +/** + * 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->dapm, ab5500_dapm_widgets, + ARRAY_SIZE(ab5500_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&codec->dapm); + return 0; +} + +static void power_for_playback(enum enum_power onoff, int ifsel) +{ + dev_dbg(ab5500_dev, "%s: interface %d power %s.\n", __func__, + ifsel, onoff == POWER_ON ? "on" : "off"); + if (mutex_lock_interruptible(&ab5500_pm_mutex)) { + dev_warn(ab5500_dev, + "%s: Signal received while waiting on the PM mutex.\n", + __func__); + return; + } + mask_set_reg(ENV_THR, ENV_THR_HIGH_MASK, 0x0F << ENV_THR_HIGH_SHIFT); + mask_set_reg(ENV_THR, ENV_THR_LOW_MASK, 0x00 << ENV_THR_LOW_SHIFT); + mask_set_reg(DC_CANCEL, DC_CANCEL_AUXO12_MASK, + 0x01 << DC_CANCEL_AUXO12_SHIFT); + + 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 int enable_regulator(enum regulator_idx idx) +{ + int ret; + + if (reg_enabled[idx]) + return 0; + + ret = regulator_enable(reg_info[idx].consumer); + if (ret != 0) { + pr_err("%s: Failure to enable regulator '%s' (ret = %d)\n", + __func__, reg_info[idx].supply, ret); + return ret; + }; + + reg_enabled[idx] = true; + pr_debug("%s: Enabled regulator '%s', status: %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1]); + return 0; +} + +static void disable_regulator(enum regulator_idx idx) +{ + if (!reg_enabled[idx]) + return; + + regulator_disable(reg_info[idx].consumer); + + reg_enabled[idx] = false; + pr_debug("%s: Disabled regulator '%s', status: %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1]); +} + +static void power_for_capture(enum enum_power onoff, int ifsel) +{ + int err; + int mask; + + dev_info(ab5500_dev, "%s: interface %d power %s", __func__, + ifsel, onoff == POWER_ON ? "on" : "off"); + 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); + + mask = (read_reg(TX2) & TXx_MUX_MASK) >> TXx_MUX_SHIFT; + + switch (onoff << 2 | mask) { + case 0: /* Power off : Amic */ + disable_regulator(REGULATOR_AMIC); + break; + case 1: /* Power off : Dmic */ + case 2: + disable_regulator(REGULATOR_DMIC); + break; + case 4: /* Power on : Amic */ + err = enable_regulator(REGULATOR_AMIC); + if (err < 0) + goto unlock; + break; + case 5: /* Power on : Dmic */ + case 6: + err = enable_regulator(REGULATOR_DMIC); + if (err < 0) + goto unlock; + break; + default: + pr_debug("%s : Not a valid regulator combination\n", + __func__); + break; + } +unlock: + 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); + + pr_info("%s: %s called.\n", __FILE__, __func__); + 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; + } + dev_info(ab5500_dev, "%s called.\n", __func__); + 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) +{ + u8 value = (dai->id == 1) ? INTERFACE1 : INTERFACE0; + + dev_info(ab5500_dev, "%s called.\n", __func__); + + /* Configure registers for either playback or capture */ + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + !(ab5500_codec_privates[dai->id].playback_active == true)) { + power_for_playback(POWER_ON, dai->id); + ab5500_codec_privates[dai->id].playback_active = true; + mask_set_reg(value, I2Sx_TRISTATE_MASK, + 0 << I2Sx_TRISTATE_SHIFT); + } else if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) && + !(ab5500_codec_privates[dai->id].capture_active == true)) { + power_for_capture(POWER_ON, dai->id); + ab5500_codec_privates[dai->id].capture_active = true; + mask_set_reg(value, I2Sx_TRISTATE_MASK, + 0 << I2Sx_TRISTATE_SHIFT); + + } + mutex_lock(&ab5500_clk_mutex); + ab5500_clk_request++; + if (ab5500_clk_request == 1) + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 1 << CLOCK_ENABLE_SHIFT); + mutex_unlock(&ab5500_clk_mutex); + + dump_registers(__func__, RX1, AUXO1_ADDER, RX2, + AUXO2_ADDER, RX1_DPGA, RX2_DPGA, AUXO1, AUXO2, -1); + + return 0; +} + +static void ab5500_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 iface = (dai->id == 0) ? INTERFACE0 : INTERFACE1; + dev_info(ab5500_dev, "%s called.\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + power_for_playback(POWER_OFF, dai->id); + ab5500_codec_privates[dai->id].playback_active = false; + } else { + power_for_capture(POWER_OFF, dai->id); + ab5500_codec_privates[dai->id].capture_active = false; + } + if (!dai->playback_active && !dai->capture_active && + (read_reg(iface) & I2Sx_MODE_MASK) == 0) { + mask_set_reg(iface, I2Sx_TRISTATE_MASK, + 1 << I2Sx_TRISTATE_SHIFT); + mask_set_reg(iface, MASTER_GENx_PWR_MASK, 0); + } + mutex_lock(&ab5500_clk_mutex); + ab5500_clk_request--; + if (ab5500_clk_request == 0) + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0 << CLOCK_ENABLE_SHIFT); + mutex_unlock(&ab5500_clk_mutex); +} + +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; + dev_info(ab5500_dev, "%s called.\n", __func__); + + 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; + mask_set_reg(iface, I2Sx_MODE_MASK, val); + 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->dapm); + return 0; +} + +#ifdef CONFIG_PM +static int ab5500_codec_suspend(struct snd_soc_codec *codec, + pm_message_t state) +{ + if (!ab5500_clk_request) + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0); + return 0; +} + +static int ab5500_codec_resume(struct snd_soc_codec *codec) +{ + if (ab5500_clk_request) + 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 AUXO3_ADDER: + case AUXO4_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 (count_ones(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, 0, 0, 0); + update_widgets_link(LINK, widget_rx1, widget_dac1, 0, 0, 0); + update_widgets_link(LINK, widget_dac1, widget_auxo1, 0, 0, 0); + + /* if0_dld_r -> rx2 -> dac2 -> auxo2 */ + update_widgets_link(LINK, widget_if0_dld_r, widget_rx2, 0, 0, 0); + update_widgets_link(LINK, widget_rx2, widget_dac2, 0, 0, 0); + update_widgets_link(LINK, widget_dac2, widget_auxo2, 0, 0, 0); + + /* Earpiece */ + update_widgets_link(LINK, widget_dac1, widget_ear, 0, 0, 0); + + /* if1_dld_l -> rx3 -> dac3 -> spkr1 */ + update_widgets_link(LINK, widget_if1_dld_l, widget_rx3, 0, 0, 0); + update_widgets_link(LINK, widget_rx3, widget_dac3, 0, 0, 0); + update_widgets_link(LINK, widget_dac3, widget_spkr1, 0, 0, 0); + +} + +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); + + /* mic2 inputs -> mic2 */ + update_widgets_link(LINK, widget_mic2p2, widget_mic2, 0, 0, 0); + update_widgets_link(LINK, widget_mic2n2, widget_mic2, 0, 0, 0); + + /* mic2 -> adc2 -> tx2 */ + update_widgets_link(LINK, widget_mic2, widget_adc2, 0, 0, 0); + update_widgets_link(LINK, widget_adc2, widget_tx2, 0, 0, 0); + + /* tx2 -> if0_uld_l & if0_uld_r */ + update_widgets_link(LINK, widget_tx2, widget_if0_uld_l, 0, 0, 0); + update_widgets_link(LINK, widget_tx2, widget_if0_uld_r, 0, 0, 0); +} + +static int create_regulators(void) +{ + int i, status = 0; + + pr_debug("%s: Enter.\n", __func__); + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) + reg_info[i].consumer = NULL; + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + reg_info[i].consumer = regulator_get(ab5500_dev, + reg_info[i].supply); + if (IS_ERR(reg_info[i].consumer)) { + status = PTR_ERR(reg_info[i].consumer); + pr_err("%s: ERROR: Failed to get regulator '%s' (ret = %d)!\n", + __func__, reg_info[i].supply, status); + reg_info[i].consumer = NULL; + goto err_get; + } + } + + return 0; + +err_get: + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + if (reg_info[i].consumer) { + regulator_put(reg_info[i].consumer); + reg_info[i].consumer = NULL; + } + } + + return status; +} + +static int __devinit ab5500_platform_probe(struct platform_device *pdev) +{ + int ret = 0; + u8 reg; + struct ab5500_codec_dai_data *codec_drvdata; + int status; + + pr_info("%s invoked with pdev = %p.\n", __func__, pdev); + ab5500_dev = &pdev->dev; + + status = create_regulators(); + if (status < 0) { + pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n", + __func__, status); + return status; + } + 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(INTERFACE0, I2Sx_TRISTATE_MASK, 1 << I2Sx_TRISTATE_SHIFT); + mask_set_reg(INTERFACE1, I2Sx_TRISTATE_MASK, 1 << I2Sx_TRISTATE_SHIFT); + + printk(KERN_ERR "Clock Setting ab5500\n"); + init_playback_route(); + init_capture_route(); + memset(&pm_stack, 0, sizeof(pm_stack)); + return ret; +} + +static int __devexit ab5500_platform_remove(struct platform_device *pdev) +{ + pr_info("%s called.\n", __func__); + regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info); + 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; + + pr_info("%s called.\n", __func__); + + /* 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) +{ + pr_info("%s called.\n", __func__); + 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"); diff --git a/sound/soc/codecs/ab5500.h b/sound/soc/codecs/ab5500.h new file mode 100644 index 00000000000..bb37798b8c7 --- /dev/null +++ b/sound/soc/codecs/ab5500.h @@ -0,0 +1,408 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Register definitions for AB5500 codec + * Author: Xie Xiaolei <xie.xiaolei@etericsson.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. + */ +#ifndef AB5500_CODEC_REGISTERS_H +#define AB5500_CODEC_REGISTERS_H + +#define AB5500_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define AB5500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + + +/* MIC BIAS */ + +#define MIC_BIAS1 0x00 +#define MIC_BIAS2 0x01 +#define MBIAS2_OUT_V_MASK 0x04 +#define MBIAS2_OUT_V_SHIFT 2 +#define MBIASx_PWR_MASK 0x02 +#define MBIASx_PWR_SHIFT 1 +#define MBIASx_PDN_IMP_MASK 0x01 +#define MBIASx_PDN_IMP_SHIFT 0 + +#define MIC_BIAS2_VAD 0x02 +#define MBIAS2_R_INT_MASK 0x01 +#define MBIAS2_R_INT_SHIFT 0 + +/* MIC */ +#define MIC1_GAIN 0x03 +#define MIC2_GAIN 0x04 +#define MICx_GAIN_MASK 0xF0 +#define MICx_GAIN_SHIFT 4 +#define MICx_IN_IMP_MASK 0x0C +#define MICx_IN_IMP_SHIFT 2 +#define MICx_PWR_MASK 0x01 +#define MICx_PWR_SHIFT 0 + +#define MIC1_INPUT_SELECT 0x05 +#define MIC2_INPUT_SELECT 0x06 +#define MICxP1_SEL_MASK 0x80 +#define MICxP1_SEL_SHIFT 7 +#define MICxN1_SEL_MASK 0x40 +#define MICxN1_SEL_SHIFT 6 +#define MICxP2_SEL_MASK 0x20 +#define MICxP2_SEL_SHIFT 5 +#define MICxN2_SEL_MASK 0x10 +#define MICxN2_SEL_SHIFT 4 +#define LINEIN_SEL_MASK 0x03 +#define LINEIN_SEL_SHIFT 0 + +#define MIC1_VMID_SELECT 0x07 +#define MIC2_VMID_SELECT 0x08 +#define VMIDx_ENABLE_MASK 0xC0 +#define VMIDx_ENABLE_SHIFT 6 +#define VMIDx_LINEIN1_N_MASK 0x20 +#define VMIDx_LINEIN1_N_SHIFT 5 +#define VMIDx_LINEIN2_N_MASK 0x10 +#define VMIDx_LINEIN2_N_SHIFT 4 +#define VMIDx_MICxP1_MASK 0x08 +#define VMIDx_MICxP1_SHIFT 3 +#define VMIDx_MICxP2_MASK 0x04 +#define VMIDx_MICxP2_SHIFT 2 +#define VMIDx_MICxN1_MASK 0x02 +#define VMIDx_MICxN1_SHIFT 1 +#define VMIDx_MICxN2_MASK 0x01 +#define VMIDx_MICxN2_SHIFT 0 + +#define MIC2_TO_MIC1 0x09 +#define MIC2_TO_MIC1_MASK 0x03 +#define MIC2_TO_MIC1_SHIFT 0 + +/* Analog Loop */ +#define ANALOG_LOOP_PGA1 0x0A +#define ANALOG_LOOP_PGA2 0x0B +#define APGAx_GAIN_MASK 0xF8 +#define APGAx_GAIN_SHIFT 3 +#define APGAx_PWR_MASK 0x04 +#define APGAx_PWR_SHIFT 2 +#define APGAx_MUX_MASK 0x03 +#define APGAx_MUX_SHIFT 0 +#define APGAx_MUX_MIC1_MASK 0x01 +#define APGAx_MUX_MIC1_SHIFT 0 +#define APGAx_MUX_MIC2_MASK 0x02 +#define APGAx_MUX_MIC2_SHIFT 1 + +#define APGA_VMID_SELECT 0x0C +#define VMID_APGA1_ENABLE_MASK 0xC0 +#define VMID_APGA1_ENABLE_SHIFT 6 +#define VMID_APGA1_LINEIN1_MASK 0x20 +#define VMID_APGA1_LINEIN1_SHIFT 5 +#define VMID_APGA2_ENABLE_MASK 0x0C +#define VMID_APGA2_ENABLE_SHIFT 2 +#define VMID_APGA2_LINEIN2_MASK 0x02 +#define VMID_APGA2_LINEIN2_SHIFT 1 + +/* Output Amplifiers */ +#define EAR_PWR 0x0D +#define EAR_PWR_MODE_MASK 0xC0 +#define EAR_PWR_MODE_SHIFT 6 +#define EAR_PWR_VMID_MASK 0x30 +#define EAR_PWR_VMID_SHIFT 4 +#define EAR_PWR_MASK 0x01 +#define EAR_PWR_SHIFT 0 + +#define EAR_GAIN 0x0E +#define EAR_GAIN_MASK 0x1F +#define EAR_GAIN_SHIFT 0 + +#define AUXO1 0x0F +#define AUXO2 0x10 +#define AUXO3 0x11 +#define AUXO4 0x12 +#define AUXOx_PWR_MASK 0x80 +#define AUXOx_PWR_SHIFT 7 +#define AUXOx_INV_MASK 0x40 +#define AUXOx_INV_SHIFT 6 +#define AUXOx_PULLDOWN_MASK 0x20 +#define AUXOx_PULLDOWN_SHIFT 5 +#define AUXOx_GAIN_MASK 0x0F +#define AUXOx_GAIN_SHIFT 0 + +#define AUXO12_PWR_MODE 0x13 +#define AUXO34_PWR_MODE 0x14 +#define AUXOxy_PWR_MODE_MASK 0x07 +#define AUXOxy_PWR_MODE_SHIFT 0 + +#define NEG_CHARGE_PUMP 0x15 +#define NEG_CHARGE_PUMP_MODE_MASK 0x02 +#define NEG_CHARGE_PUMP_MODE_SHIFT 1 +#define NEG_CHARGE_PUMP_PWR_MASK 0x01 +#define NEG_CHARGE_PUMP_PWR_SHIFT 0 + +#define ENV_THR 0x16 +#define ENV_THR_HIGH_MASK 0xF0 +#define ENV_THR_HIGH_SHIFT 4 +#define ENV_THR_LOW_MASK 0x0F +#define ENV_THR_LOW_SHIFT 0 + +#define ENV_DECAY_TIME 0x17 +#define ENV_DECAY_TIME_CP_LV_MASK 0x20 +#define ENV_DECAY_TIME_CP_LV_SHIFT 5 +#define ENV_DECAY_TIME_DET_CP_MASK 0x10 +#define ENV_DECAY_TIME_DET_CP_SHIFT 4 +#define ENV_DECAY_TIME_MASK 0x0F +#define ENV_DECAY_TIME_SHIFT 0 + +#define DC_CANCEL 0x18 +#define DC_CANCEL_SPKR2_MASK 0x10 +#define DC_CANCEL_SPKR2_SHIFT 4 +#define DC_CANCEL_SPKR1_MASK 0x08 +#define DC_CANCEL_SPKR1_SHIFT 3 +#define DC_CANCEL_AUXO34_MASK 0x04 +#define DC_CANCEL_AUXO34_SHIFT 2 +#define DC_CANCEL_AUXO12_MASK 0x02 +#define DC_CANCEL_AUXO12_SHIFT 1 +#define DC_CANCEL_OFFSET_CLOCK_MASK 0x01 +#define DC_CANCEL_OFFSET_CLOCK_SHIFT 0 + +#define SPKR1 0x19 +#define SPKR2 0x1A +#define SPKRx_PWR_MASK 0xC0 +#define SPKRx_PWR_SHIFT 6 +#define SPKRx_PWR_VBR_VALUE 0x40 +#define SPKRx_PWR_CLS_D_VALUE 0x80 +#define SPKRx_PWR_CLS_AB_VALUE 0xC0 +#define SPKR1_VMID_MASK 0x20 +#define SPKR1_VMID_SHIFT 5 +#define SPKRx_GAIN_MASK 0x1F +#define SPKRx_GAIN_SHIFT 0 + +#define SPKR_OVCR 0x1B +#define SPKR_OVCR_PROT2_MASK 0x80 +#define SPKR_OVCR_PROT2_SHIFT 7 +#define SPKR_OVCR_TRIM2_MASK 0x70 +#define SPKR_OVCR_TRIM2_SHIFT 4 +#define SPKR_OVCR_PROT1_MASK 0x08 +#define SPKR_OVCR_PROT1_SHIFT 3 +#define SPKR_OVCR_TRIM1_MASK 0x07 +#define SPKR_OVCR_TRIM1_SHIFT 0 + +#define PWMCTRL_SPKR1 0x1C +#define PWMCTRL_SPKR2 0x1F +#define PWMCTRL_SPKRx_N1_POL_MASK 0x80 +#define PWMCTRL_SPKRx_N1_POL_SHIFT 7 +#define PWMCTRL_SPKRx_P1_POL_MASK 0x40 +#define PWMCTRL_SPKRx_P1_POL_SHIFT 6 +#define PWMCTRL_SPKRx_MASK 0x04 +#define PWMCTRL_SPKRx_SHIFT 2 +#define PWMCTRL_SPKRxN_MASK 0x02 +#define PWMCTRL_SPKRxN_SHIFT 1 +#define PWMCTRL_SPKRxP_MASK 0x01 +#define PWMCTRL_SPKRxP_SHIFT 0 + +#define PWM_SPKR1N 0x1D +#define PWM_SPKR2N 0x20 +#define PWM_SPKR1P 0x1E +#define PWM_SPKR2P 0x21 +#define PWM_SPKRxy_DUT_CYC_MASK 0xFF +#define PWM_SPKRxy_DUT_CYC_SHIFT 0 + +#define SPKR1_CLK_DIV 0x22 +#define SPKR2_CLK_DIV 0x23 +#define SPKRx_CLK_DIV_MASK 0x3F +#define SPKRx_CLK_DIV_SHIFT 0 + +#define LINE1 0x24 +#define LINE2 0x25 +#define LINEx_PWR_MASK 0x80 +#define LINEx_PWR_SHIFT 7 +#define LINEx_INV_MASK 0x40 +#define LINEx_INV_SHIFT 6 +#define LINEx_TO_USB_MASK 0x20 +#define LINEx_TO_USB_SHIFT 5 +#define LINEx_VMID_BUFF_MASK 0x10 +#define LINEx_VMID_BUFF_SHIFT 4 +#define LINEx_GAIN_MASK 0x0F +#define LINEx_GAIN_SHIFT 0 + +#define USB_AUDIO 0x26 +#define USB_AUDIO_MIC_MUX_MASK 0x03 +#define USB_AUDIO_MIC_MUX_SHIFT 0 + +#define EAR_ADDER 0x28 +#define AUXO1_ADDER 0x29 +#define AUXO2_ADDER 0x2A +#define AUXO3_ADDER 0x2B +#define AUXO4_ADDER 0x2C +#define SPKR1_ADDER 0x2D +#define SPKR2_ADDER 0x2E +#define LINE1_ADDER 0x2F +#define LINE2_ADDER 0x30 +#define APGA2_TO_X_MASK 0x10 +#define APGA2_TO_X_SHIFT 4 +#define APGA1_TO_X_MASK 0x08 +#define APGA1_TO_X_SHIFT 3 +#define DAC3_TO_X_MASK 0x04 +#define DAC3_TO_X_SHIFT 2 +#define DAC2_TO_X_MASK 0x02 +#define DAC2_TO_X_SHIFT 1 +#define DAC1_TO_X_MASK 0x01 +#define DAC1_TO_X_SHIFT 0 + +#define EAR_TO_MIC2 0x31 +#define SPKR1_TO_MIC2 0x32 +#define SPKR2_TO_MIC2 0x33 +#define EAR_TO_MIC2_MASK 0x01 +#define EAR_TO_MIC2_SHIFT 0 + +#define ADC_LOW_PWR 0x35 +#define ADC_LOW_PWR_MASK 0x01 +#define ADC_LOW_PWR_SHIFT 0 + +#define TX1 0x36 +#define TX2 0x37 +#define TXx_MUX_MASK 0x60 +#define TXx_MUX_SHIFT 5 +#define TXx_FS_MASK 0x10 +#define TXx_FS_SHIFT 4 +#define TXx_HP_FILTER_MASK 0x0C +#define TXx_HP_FILTER_SHIFT 2 +#define TXx_PWR_MASK 0x02 +#define TXx_PWR_SHIFT 1 +#define ADCx_PWR_MASK 0x01 +#define ADCx_PWR_SHIFT 0 + +#define RX1 0x38 +#define RX2 0x39 +#define RX3 0x3A +#define RXx_DATA_MASK 0x70 +#define RXx_DATA_SHIFT 4 +#define RXx_PWR_MASK 0x08 +#define RXx_PWR_SHIFT 3 +#define DACx_PWR_MASK 0x04 +#define DACx_PWR_SHIFT 2 +#define DACx_PWR_MODE_MASK 0x03 +#define DACx_PWR_MODE_SHIFT 0 + +#define TX_DPGA1 0x3B +#define TX_DPGA2 0x3C +#define TX_DPGAx_MASK 0x0F +#define TX_DPGAx_SHIFT 0 + +#define RX1_DPGA 0x3D +#define RX2_DPGA 0x3E +#define RX3_DPGA 0x3F +#define RXx_DPGA_MASK 0x7F +#define RXx_DPGA_SHIFT 0 + +#define ST1_PGA 0x40 +#define ST2_PGA 0x41 +#define STx_HP_FILTER_MASK 0x60 +#define STx_HP_FILTER_SHIFT 6 +#define STx_MUX_MASK 0x10 +#define STx_MUX_SHIFT 4 +#define STx_PGA_MASK 0x0F +#define STx_PGA_SHIFT 0 + +#define CLOCK 0x42 +#define CLOCK_REF_SELECT_MASK 0x02 +#define CLOCK_REF_SELECT_SHIFT 1 +#define CLOCK_ENABLE_MASK 0x01 +#define CLOCK_ENABLE_SHIFT 0 + +#define INTERFACE0 0x43 +#define INTERFACE1 0x45 +#define I2Sx_WORDLENGTH_MASK 0x40 +#define I2Sx_WORDLENGTH_SHIFT 6 +#define MASTER_GENx_PWR_MASK 0x20 +#define MASTER_GENx_PWR_SHIFT 5 +#define I2Sx_MODE_MASK 0x10 +#define I2Sx_MODE_SHIFT 4 +#define I2Sx_TRISTATE_MASK 0x08 +#define I2Sx_TRISTATE_SHIFT 3 +#define I2Sx_PULLDOWN_MASK 0x04 +#define I2Sx_PULLDOWN_SHIFT 2 +#define I2Sx_SR_MASK 0x03 +#define I2Sx_SR_SHIFT 0 +#define I2Sx_SR_8000Hz 0 +#define I2Sx_SR_16000Hz 1 +#define I2Sx_SR_44100Hz 2 +#define I2Sx_SR_48000Hz 3 + +#define INTERFACE0_ULD 0x44 +#define INTERFACE1_ULD 0x46 +#define I2Sx_ULD_R_MASK 0x70 +#define I2Sx_ULD_R_SHIFT 4 +#define I2Sx_ULD_L_MASK 0x07 +#define I2Sx_ULD_L_SHIFT 0 + +#define INTERFACE_SWAP 0x47 +#define IO_SWAP0_MASK 0x02 +#define IO_SWAP0_SHIFT 1 +#define IO_SWAP1_MASK 0x01 +#define IO_SWAP1_SHIFT 0 + +#define AB5500_FIRST_REG MIC_BIAS1 +#define AB5500_LAST_REG INTERFACE_SWAP + +#define AB5500_VIRTUAL_REG1 (AB5500_LAST_REG + 1) +#define IF0_DLD_L_PW_SHIFT 0 +#define IF0_DLD_R_PW_SHIFT 1 +#define IF0_ULD_L_PW_SHIFT 2 +#define IF0_ULD_R_PW_SHIFT 3 +#define IF1_DLD_L_PW_SHIFT 4 +#define IF1_DLD_R_PW_SHIFT 5 +#define IF1_ULD_L_PW_SHIFT 6 +#define IF1_ULD_R_PW_SHIFT 7 + +#define AB5500_VIRTUAL_REG2 (AB5500_LAST_REG + 2) +#define MIC1P1_PW_SHIFT 0 +#define MIC1N1_PW_SHIFT 1 +#define MIC1P2_PW_SHIFT 2 +#define MIC1N2_PW_SHIFT 3 +#define MIC2P1_PW_SHIFT 4 +#define MIC2N1_PW_SHIFT 5 +#define MIC2P2_PW_SHIFT 6 +#define MIC2N2_PW_SHIFT 7 + +#define AB5500_VIRTUAL_REG3 (AB5500_LAST_REG + 3) +#define SPKR1_MODE_MASK 0x03 +#define SPKR1_MODE_SHIFT 0 +#define SPKR1_MODE_VBR_VALUE 0 +#define SPKR1_MODE_CLS_D_VALUE 1 +#define SPKR1_MODE_CLS_AB_VALUE 2 +#define SPKR1_ADDER_PWR_SHIFT 2 +#define SPKR1_PWR_SHIFT 3 +#define SPKR2_MODE_MASK 0x10 +#define SPKR2_MODE_SHIFT 4 +#define SPKR2_MODE_VBR_VALUE 0 +#define SPKR2_MODE_CLS_D_VALUE 1 +#define SPKR2_ADDER_PWR_SHIFT 5 +#define SPKR2_PWR_SHIFT 6 + +#define AB5500_VIRTUAL_REG4 (AB5500_LAST_REG + 4) +#define PWM_SPKR1_PWR_SHIFT 0 +#define PWM_SPKR2_PWR_SHIFT 1 +#define PWM_SPKR1N_PWR_SHIFT 2 +#define PWM_SPKR1P_PWR_SHIFT 3 +#define PWM_SPKR2N_PWR_SHIFT 4 +#define PWM_SPKR2P_PWR_SHIFT 5 + +#define AB5500_VIRTUAL_REG5 (AB5500_LAST_REG + 5) +#define PWM_SPKR1N_SEL_SHIFT 0 +#define PWM_SPKR1P_SEL_SHIFT 1 +#define PWM_SPKR2N_SEL_SHIFT 2 +#define PWM_SPKR2P_SEL_SHIFT 3 + +#define DUMMY_REG 0xff + +/* #define SPKR1_PWR_VBR_SHIFT 0 */ +/* #define SPKR1_PWR_CLS_D_SHIFT 1 */ +/* #define SPKR1_PWR_CLS_AB_SHIFT 2 */ +/* #define SPKR2_PWR_VBR_SHIFT 3 */ +/* #define SPKR2_PWR_CLS_D_SHIFT 4 */ +/* #define SPKR2_PWR_CLS_AB_SHIFT 5 */ + +#endif diff --git a/sound/soc/codecs/ab8500_audio.c b/sound/soc/codecs/ab8500_audio.c new file mode 100644 index 00000000000..2d2c972549e --- /dev/null +++ b/sound/soc/codecs/ab8500_audio.c @@ -0,0 +1,2957 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Mikko J. Lehto <mikko.lehto@symbio.com>, + * Mikko Sarmanne <mikko.sarmanne@symbio.com>, + * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>, + * Ola Lilja <ola.o.lilja@stericsson.com>, + * Kristoffer Karlsson <kristoffer.karlsson@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/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/mutex.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 <sound/tlv.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab8500-sysctrl.h> +#include "ab8500_audio.h" + +/* To convert register definition shifts to masks */ +#define BMASK(bsft) (1 << (bsft)) + +/* Macrocell value definitions */ +#define CLK_32K_OUT2_DISABLE 0x01 +#define INACTIVE_RESET_AUDIO 0x02 +#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10 +#define ENABLE_VINTCORE12_SUPPLY 0x04 +#define GPIO27_DIR_OUTPUT 0x04 +#define GPIO29_DIR_OUTPUT 0x10 +#define GPIO31_DIR_OUTPUT 0x40 + +/* Macrocell register definitions */ +#define AB8500_CTRL3_REG 0x0200 +#define AB8500_GPIO_DIR4_REG 0x1013 + +/* Nr of FIR/IIR-coeff banks in ANC-block */ +#define AB8500_NR_OF_ANC_COEFF_BANKS 2 + +/* Minimum duration to keep ANC IIR Init bit high or +low before proceeding with the configuration sequence */ +#define AB8500_ANC_SM_DELAY 2000 + +/* + * AB8500 register cache & default register settings + */ +static const u8 ab8500_reg_cache[AB8500_CACHEREGNUM] = { + 0x00, /* REG_POWERUP (0x00) */ + 0x00, /* REG_AUDSWRESET (0x01) */ + 0x00, /* REG_ADPATHENA (0x02) */ + 0x00, /* REG_DAPATHENA (0x03) */ + 0x00, /* REG_ANACONF1 (0x04) */ + 0x0F, /* REG_ANACONF2 (0x05) */ + 0x00, /* REG_DIGMICCONF (0x06) */ + 0x00, /* REG_ANACONF3 (0x07) */ + 0x00, /* REG_ANACONF4 (0x08) */ + 0x00, /* REG_DAPATHCONF (0x09) */ + 0x40, /* REG_MUTECONF (0x0A) */ + 0x01, /* REG_SHORTCIRCONF (0x0B) */ + 0x01, /* REG_ANACONF5 (0x0C) */ + 0x00, /* REG_ENVCPCONF (0x0D) */ + 0x00, /* REG_SIGENVCONF (0x0E) */ + 0x3F, /* REG_PWMGENCONF1 (0x0F) */ + 0x32, /* REG_PWMGENCONF2 (0x10) */ + 0x32, /* REG_PWMGENCONF3 (0x11) */ + 0x32, /* REG_PWMGENCONF4 (0x12) */ + 0x32, /* REG_PWMGENCONF5 (0x13) */ + 0x0F, /* REG_ANAGAIN1 (0x14) */ + 0x0F, /* REG_ANAGAIN2 (0x15) */ + 0x22, /* REG_ANAGAIN3 (0x16) */ + 0x55, /* REG_ANAGAIN4 (0x17) */ + 0x13, /* REG_DIGLINHSLGAIN (0x18) */ + 0x13, /* REG_DIGLINHSRGAIN (0x19) */ + 0x00, /* REG_ADFILTCONF (0x1A) */ + 0x00, /* REG_DIGIFCONF1 (0x1B) */ + 0x02, /* REG_DIGIFCONF2 (0x1C) */ + 0x00, /* REG_DIGIFCONF3 (0x1D) */ + 0x02, /* REG_DIGIFCONF4 (0x1E) */ + 0xCC, /* REG_ADSLOTSEL1 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL2 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL3 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL4 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL5 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL6 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL7 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL8 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL9 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL10 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL11 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL12 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL13 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL14 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL15 (0xCC) */ + 0xCC, /* REG_ADSLOTSEL16 (0xCC) */ + 0x00, /* REG_ADSLOTHIZCTRL1 (0x2F) */ + 0x00, /* REG_ADSLOTHIZCTRL2 (0x30) */ + 0x00, /* REG_ADSLOTHIZCTRL3 (0x31) */ + 0x00, /* REG_ADSLOTHIZCTRL4 (0x32) */ + 0x08, /* REG_DASLOTCONF1 (0x33) */ + 0x08, /* REG_DASLOTCONF2 (0x34) */ + 0x08, /* REG_DASLOTCONF3 (0x35) */ + 0x08, /* REG_DASLOTCONF4 (0x36) */ + 0x08, /* REG_DASLOTCONF5 (0x37) */ + 0x08, /* REG_DASLOTCONF6 (0x38) */ + 0x08, /* REG_DASLOTCONF7 (0x39) */ + 0x08, /* REG_DASLOTCONF8 (0x3A) */ + 0x00, /* REG_CLASSDCONF1 (0x3B) */ + 0x00, /* REG_CLASSDCONF2 (0x3C) */ + 0x84, /* REG_CLASSDCONF3 (0x3D) */ + 0x00, /* REG_DMICFILTCONF (0x3E) */ + 0xFE, /* REG_DIGMULTCONF1 (0x3F) */ + 0xC0, /* REG_DIGMULTCONF2 (0x40) */ + 0x3F, /* REG_ADDIGGAIN1 (0x41) */ + 0x3F, /* REG_ADDIGGAIN2 (0x42) */ + 0x1F, /* REG_ADDIGGAIN3 (0x43) */ + 0x1F, /* REG_ADDIGGAIN4 (0x44) */ + 0x3F, /* REG_ADDIGGAIN5 (0x45) */ + 0x3F, /* REG_ADDIGGAIN6 (0x46) */ + 0x1F, /* REG_DADIGGAIN1 (0x47) */ + 0x1F, /* REG_DADIGGAIN2 (0x48) */ + 0x3F, /* REG_DADIGGAIN3 (0x49) */ + 0x3F, /* REG_DADIGGAIN4 (0x4A) */ + 0x3F, /* REG_DADIGGAIN5 (0x4B) */ + 0x3F, /* REG_DADIGGAIN6 (0x4C) */ + 0x3F, /* REG_ADDIGLOOPGAIN1 (0x4D) */ + 0x3F, /* REG_ADDIGLOOPGAIN2 (0x4E) */ + 0x00, /* REG_HSLEARDIGGAIN (0x4F) */ + 0x00, /* REG_HSRDIGGAIN (0x50) */ + 0x1F, /* REG_SIDFIRGAIN1 (0x51) */ + 0x1F, /* REG_SIDFIRGAIN2 (0x52) */ + 0x00, /* REG_ANCCONF1 (0x53) */ + 0x00, /* REG_ANCCONF2 (0x54) */ + 0x00, /* REG_ANCCONF3 (0x55) */ + 0x00, /* REG_ANCCONF4 (0x56) */ + 0x00, /* REG_ANCCONF5 (0x57) */ + 0x00, /* REG_ANCCONF6 (0x58) */ + 0x00, /* REG_ANCCONF7 (0x59) */ + 0x00, /* REG_ANCCONF8 (0x5A) */ + 0x00, /* REG_ANCCONF9 (0x5B) */ + 0x00, /* REG_ANCCONF10 (0x5C) */ + 0x00, /* REG_ANCCONF11 (0x5D) - read only */ + 0x00, /* REG_ANCCONF12 (0x5E) - read only */ + 0x00, /* REG_ANCCONF13 (0x5F) - read only */ + 0x00, /* REG_ANCCONF14 (0x60) - read only */ + 0x00, /* REG_SIDFIRADR (0x61) */ + 0x00, /* REG_SIDFIRCOEF1 (0x62) */ + 0x00, /* REG_SIDFIRCOEF2 (0x63) */ + 0x00, /* REG_SIDFIRCONF (0x64) */ + 0x00, /* REG_AUDINTMASK1 (0x65) */ + 0x00, /* REG_AUDINTSOURCE1 (0x66) - read only */ + 0x00, /* REG_AUDINTMASK2 (0x67) */ + 0x00, /* REG_AUDINTSOURCE2 (0x68) - read only */ + 0x00, /* REG_FIFOCONF1 (0x69) */ + 0x00, /* REG_FIFOCONF2 (0x6A) */ + 0x00, /* REG_FIFOCONF3 (0x6B) */ + 0x00, /* REG_FIFOCONF4 (0x6C) */ + 0x00, /* REG_FIFOCONF5 (0x6D) */ + 0x00, /* REG_FIFOCONF6 (0x6E) */ + 0x02, /* REG_AUDREV (0x6F) - read only */ +}; + +static struct snd_soc_codec *ab8500_codec; + +/* ADCM */ +static const u8 ADCM_ANACONF5_MASK = BMASK(REG_ANACONF5_ENCPHS); +static const u8 ADCM_MUTECONF_MASK = BMASK(REG_MUTECONF_MUTHSL) | + BMASK(REG_MUTECONF_MUTHSR); +static const u8 ADCM_ANACONF4_MASK = BMASK(REG_ANACONF4_ENHSL) | + BMASK(REG_ANACONF4_ENHSR); +static unsigned int adcm_anaconf5, adcm_muteconf, adcm_anaconf4; +static int adcm = AB8500_AUDIO_ADCM_NORMAL; + +/* Signed multi register array controls. */ +struct soc_smra_control { + unsigned int *reg; + const unsigned int rcount, count, invert; + long min, max; + const char **texts; + long *values; +}; + +/* Sidetone FIR-coeff cache */ +static long sid_fir_cache[REG_SID_FIR_COEFFS]; + +/* ANC FIR- & IIR-coeff caches */ +static long anc_fir_cache[REG_ANC_FIR_COEFFS]; +static long anc_iir_cache[REG_ANC_IIR_COEFFS]; + +/* Reads an arbitrary register from the ab8500 chip. +*/ +static int ab8500_codec_read_reg(struct snd_soc_codec *codec, unsigned int bank, + unsigned int reg) +{ + u8 value; + int status = abx500_get_register_interruptible( + codec->dev, bank, reg, &value); + + if (status < 0) { + pr_err("%s: Register (%02x:%02x) read failed (%d).\n", + __func__, (u8)bank, (u8)reg, status); + } else { + pr_debug("Read 0x%02x from register %02x:%02x\n", + (u8)value, (u8)bank, (u8)reg); + status = value; + } + + return status; +} + +/* Writes an arbitrary register to the ab8500 chip. + */ +static int ab8500_codec_write_reg(struct snd_soc_codec *codec, unsigned int bank, + unsigned int reg, unsigned int value) +{ + int status = abx500_set_register_interruptible( + codec->dev, bank, reg, value); + + if (status < 0) { + pr_err("%s: Register (%02x:%02x) write failed (%d).\n", + __func__, (u8)bank, (u8)reg, status); + } else { + pr_debug("Wrote 0x%02x into register %02x:%02x\n", + (u8)value, (u8)bank, (u8)reg); + } + + return status; +} + +/* Reads an audio register from the cache or hardware. + */ +static unsigned int ab8500_codec_read_reg_audio(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if (reg == REG_SIDFIRCONF) + return ab8500_codec_read_reg(codec, AB8500_AUDIO, reg); + + return cache[reg]; +} + +/* Writes an audio register to the hardware and cache. + */ +static int ab8500_codec_write_reg_audio(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u8 *cache = codec->reg_cache; + int status = ab8500_codec_write_reg(codec, AB8500_AUDIO, reg, value); + + if (status >= 0) + cache[reg] = value; + + return status; +} + +/* + * Updates an audio register. + * + * Returns 1 for change, 0 for no change, or negative error code. + */ +static inline int ab8500_codec_update_reg_audio(struct snd_soc_codec *codec, + unsigned int reg, unsigned int clr, unsigned int ins) +{ + unsigned int new, old; + int ret; + + old = ab8500_codec_read_reg_audio(codec, reg); + new = (old & ~clr) | ins; + if (old == new) + return 0; + + ret = ab8500_codec_write_reg_audio(codec, reg, new); + + return (ret < 0) ? ret : 1; +} + +/* Generic soc info for signed register controls. */ +int snd_soc_info_s(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_smra_control *smra = + (struct soc_smra_control *)kcontrol->private_value; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = smra->count; + uinfo->value.integer.min = smra->min; + uinfo->value.integer.max = smra->max; + + return 0; +} + +/* Generic soc get for signed multi register controls. */ +int snd_soc_get_smr(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_smra_control *smra = + (struct soc_smra_control *)kcontrol->private_value; + unsigned int *reg = smra->reg; + unsigned int rcount = smra->rcount; + long min = smra->min; + long max = smra->max; + unsigned int invert = smra->invert; + unsigned long mask = abs(min) | abs(max); + long value = 0; + int i, rvalue; + + for (i = 0; i < rcount; i++) { + rvalue = snd_soc_read(codec, reg[i]) & REG_MASK_ALL; + value |= rvalue << (8 * (rcount - i - 1)); + } + value &= mask; + if (min < 0 && value > max) + value |= ~mask; + if (invert) + value = ~value; + ucontrol->value.integer.value[0] = value; + + return 0; +} + +/* Generic soc put for signed multi register controls. */ +int snd_soc_put_smr(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_smra_control *smra = + (struct soc_smra_control *)kcontrol->private_value; + unsigned int *reg = smra->reg; + unsigned int rcount = smra->rcount; + long min = smra->min; + long max = smra->max; + unsigned int invert = smra->invert; + unsigned long mask = abs(min) | abs(max); + long value = ucontrol->value.integer.value[0]; + int i, rvalue, err; + + if (invert) + value = ~value; + if (value > max) + value = max; + else if (value < min) + value = min; + value &= mask; + for (i = 0; i < rcount; i++) { + rvalue = (value >> (8 * (rcount - i - 1))) & REG_MASK_ALL; + err = snd_soc_write(codec, reg[i], rvalue); + if (err < 0) + return 0; + } + + return 1; +} + +/* Generic soc get for signed array controls. */ +static int snd_soc_get_sa(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_smra_control *smra = + (struct soc_smra_control *)kcontrol->private_value; + long *values = smra->values; + unsigned int count = smra->count; + unsigned int idx; + + for (idx = 0; idx < count; idx++) + ucontrol->value.integer.value[idx] = values[idx]; + + return 0; +} + +/* Generic soc put for signed array controls. */ +static int snd_soc_put_sa(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_smra_control *smra = + (struct soc_smra_control *) kcontrol->private_value; + long *values = smra->values; + unsigned int count = smra->count; + long min = smra->min; + long max = smra->max; + unsigned int idx; + long value; + + for (idx = 0; idx < count; idx++) { + value = ucontrol->value.integer.value[idx]; + if (value > max) + value = max; + else if (value < min) + value = min; + values[idx] = value; + } + + return 0; +} + +/* Generic soc get for enum strobe controls. */ +int snd_soc_get_enum_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = e->reg; + unsigned int bit = e->shift_l; + unsigned int invert = e->shift_r != 0; + unsigned int value = snd_soc_read(codec, reg) & BMASK(bit); + + if (bit != 0 && value != 0) + value = value >> bit; + ucontrol->value.enumerated.item[0] = value ^ invert; + + return 0; +} + +/* Generic soc put for enum strobe controls. */ +int snd_soc_put_enum_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = e->reg; + unsigned int bit = e->shift_l; + unsigned int invert = e->shift_r != 0; + unsigned int strobe = ucontrol->value.enumerated.item[0] != 0; + unsigned int clr_mask = (strobe ^ invert) ? REG_MASK_NONE : BMASK(bit); + unsigned int set_mask = (strobe ^ invert) ? BMASK(bit) : REG_MASK_NONE; + + if (snd_soc_update_bits(codec, reg, clr_mask, set_mask) == 0) + return 0; + return snd_soc_update_bits(codec, reg, set_mask, clr_mask); +} + +static const char * const enum_ena_dis[] = {"Enabled", "Disabled"}; +static const char * const enum_dis_ena[] = {"Disabled", "Enabled"}; +static const char * const enum_rdy_apl[] = {"Ready", "Apply"}; + +/* Sidetone FIR-coefficients configuration sequence */ +static int sid_apply_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec; + unsigned int param, sidconf; + int ret = 0; + + pr_debug("%s: Enter\n", __func__); + + if (ucontrol->value.integer.value[0] != 1) { + pr_err("%s: ERROR: This control supports 'Apply' only!\n", + __func__); + return ret; + } + + codec = snd_kcontrol_chip(kcontrol); + + mutex_lock(&codec->mutex); + + sidconf = snd_soc_read(codec, REG_SIDFIRCONF); + if (((sidconf & BMASK(REG_SIDFIRCONF_FIRSIDBUSY)) != 0)) { + if ((sidconf & BMASK(REG_SIDFIRCONF_ENFIRSIDS)) == 0) { + pr_err("%s: Sidetone busy while off. Resetting...\n", + __func__); + snd_soc_update_bits(codec, REG_SIDFIRADR, + REG_MASK_NONE, BMASK(REG_SIDFIRADR_FIRSIDSET)); + snd_soc_update_bits(codec, REG_SIDFIRADR, + BMASK(REG_SIDFIRADR_FIRSIDSET), REG_MASK_NONE); + } + ret = -EBUSY; + goto out; + } + + snd_soc_write(codec, REG_SIDFIRADR, REG_MASK_NONE); + + for (param = 0; param < REG_SID_FIR_COEFFS; param++) { + snd_soc_write(codec, REG_SIDFIRCOEF1, + sid_fir_cache[param] >> 8 & REG_MASK_ALL); + snd_soc_write(codec, REG_SIDFIRCOEF2, + sid_fir_cache[param] & REG_MASK_ALL); + } + + snd_soc_update_bits(codec, REG_SIDFIRADR, + REG_MASK_NONE, BMASK(REG_SIDFIRADR_FIRSIDSET)); + snd_soc_update_bits(codec, REG_SIDFIRADR, + BMASK(REG_SIDFIRADR_FIRSIDSET), REG_MASK_NONE); + + ret = 1; +out: + mutex_unlock(&codec->mutex); + + pr_debug("%s: Exit\n", __func__); + + return ret; +} + +/* Controls - DAPM */ + +/* Inverted order - Ascending/Descending */ +enum control_inversion { + NORMAL = 0, + INVERT = 1 +}; + +/* Headset */ + +/* Headset Left - Enable/Disable */ +static const struct soc_enum enum_headset_left = SOC_ENUM_SINGLE(0, 0, 2, enum_ena_dis); +static const struct snd_kcontrol_new dapm_headset_left_mux = + SOC_DAPM_ENUM_VIRT("Headset Left", enum_headset_left); + +/* Headsett Right - Enable/Disable */ +static const struct soc_enum enum_headset_right = SOC_ENUM_SINGLE(0, 0, 2, enum_ena_dis); +static const struct snd_kcontrol_new dapm_headset_right_mux = + SOC_DAPM_ENUM_VIRT("Headset Right", enum_headset_right); + +/* Earpiece */ + +/* Earpiece - Mute */ +static const struct soc_enum enum_ear = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_ear_mux = + SOC_DAPM_ENUM_VIRT("Earpiece", enum_ear); + +/* Earpiece source selector */ +static const char * const enum_ear_lineout_source[] = {"Headset Left", "IHF Left"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ear_lineout_source, REG_DMICFILTCONF, + REG_DMICFILTCONF_DA3TOEAR, enum_ear_lineout_source); +static const struct snd_kcontrol_new dapm_ear_lineout_source[] = { + SOC_DAPM_ENUM("Earpiece or LineOut Mono Source", dapm_enum_ear_lineout_source), +}; + +/* LineOut */ + +/* LineOut source selector */ +static const char * const enum_lineout_source[] = {"Mono Path", "Stereo Path"}; +static SOC_ENUM_DOUBLE_DECL(dapm_enum_lineout_source, REG_ANACONF5, + REG_ANACONF5_HSLDACTOLOL, REG_ANACONF5_HSRDACTOLOR, enum_lineout_source); +static const struct snd_kcontrol_new dapm_lineout_source[] = { + SOC_DAPM_ENUM("LineOut Source", dapm_enum_lineout_source), +}; + +/* LineOut */ + +/* LineOut Left - Enable/Disable */ +static const struct soc_enum enum_lineout_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_lineout_left_mux = + SOC_DAPM_ENUM_VIRT("LineOut Left", enum_lineout_left); + +/* LineOut Right - Enable/Disable */ +static const struct soc_enum enum_lineout_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_lineout_right_mux = + SOC_DAPM_ENUM_VIRT("LineOut Right", enum_lineout_right); + +/* LineOut/IHF - Select */ +static const char * const enum_ihf_or_lineout_select_sel[] = {"IHF", "LineOut"}; +static const struct soc_enum enum_ihf_or_lineout_select = SOC_ENUM_SINGLE(0, 0, 2, enum_ihf_or_lineout_select_sel); +static const struct snd_kcontrol_new dapm_ihf_or_lineout_select_mux = + SOC_DAPM_ENUM_VIRT("IHF or LineOut Select", enum_ihf_or_lineout_select); + + +/* IHF */ + +/* IHF - Enable/Disable */ +static const struct soc_enum enum_ihf_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_ihf_left_mux = + SOC_DAPM_ENUM_VIRT("IHF Left", enum_ihf_left); + +static const struct soc_enum enum_ihf_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_ihf_right_mux = + SOC_DAPM_ENUM_VIRT("IHF Right", enum_ihf_right); + +/* IHF left - ANC selector */ +static const char * const enum_ihfx_sel[] = {"Audio Path", "ANC"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfl_sel, REG_DIGMULTCONF2, + REG_DIGMULTCONF2_HFLSEL, enum_ihfx_sel); +static const struct snd_kcontrol_new dapm_ihfl_select[] = { + SOC_DAPM_ENUM("IHF Left Source", dapm_enum_ihfl_sel), +}; + +/* IHF right - ANC selector */ +static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfr_sel, REG_DIGMULTCONF2, + REG_DIGMULTCONF2_HFRSEL, enum_ihfx_sel); +static const struct snd_kcontrol_new dapm_ihfr_select[] = { + SOC_DAPM_ENUM("IHF Right Source", dapm_enum_ihfr_sel), +}; + +/* Mic 1 */ + +/* Mic 1 - Mute */ +static const struct soc_enum enum_mic1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_mic1_mux = + SOC_DAPM_ENUM_VIRT("Mic 1", enum_mic1); + +/* Mic 1 - Mic 1A or 1B selector */ +static const char * const enum_mic1ab_sel[] = {"Mic 1A", "Mic 1B"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_mic1ab_sel, REG_ANACONF3, + REG_ANACONF3_MIC1SEL, enum_mic1ab_sel); +static const struct snd_kcontrol_new dapm_mic1ab_select[] = { + SOC_DAPM_ENUM("Mic 1A or 1B Select", dapm_enum_mic1ab_sel), +}; + +/* Mic 1 - AD3 - Mic 1 or DMic 3 selector */ +static const char * const enum_ad3_sel[] = {"Mic 1", "DMic 3"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad3_sel, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_AD3SEL, enum_ad3_sel); +static const struct snd_kcontrol_new dapm_ad3_select[] = { + SOC_DAPM_ENUM("AD 3 Select", dapm_enum_ad3_sel), +}; + +/* Mic 1 - AD6 - Mic 1 or DMic 6 selector */ +static const char * const enum_ad6_sel[] = {"Mic 1", "DMic 6"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad6_sel, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_AD6SEL, enum_ad6_sel); +static const struct snd_kcontrol_new dapm_ad6_select[] = { + SOC_DAPM_ENUM("AD 6 Select", dapm_enum_ad6_sel), +}; + +/* Mic 2 */ + +/* Mic 2 - Mute */ +static const struct soc_enum enum_mic2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_mic2_mux = + SOC_DAPM_ENUM_VIRT("Mic 2", enum_mic2); + +/* Mic 2 - AD5 - Mic 2 or DMic 5 selector */ +static const char * const enum_ad5_sel[] = {"Mic 2", "DMic 5"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad5_sel, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_AD5SEL, enum_ad5_sel); +static const struct snd_kcontrol_new dapm_ad5_select[] = { + SOC_DAPM_ENUM("AD 5 Select", dapm_enum_ad5_sel), +}; + +/* LineIn */ + +/* LineIn left - Mute */ +static const struct soc_enum enum_linl = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_linl_mux = + SOC_DAPM_ENUM_VIRT("LineIn Left", enum_linl); + +static const struct snd_kcontrol_new dapm_linl_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTLINL, 1, INVERT), +}; + +/* LineIn left - AD1 - LineIn Left or DMic 1 selector */ +static const char * const enum_ad1_sel[] = {"LineIn Left", "DMic 1"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad1_sel, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_AD1SEL, enum_ad1_sel); +static const struct snd_kcontrol_new dapm_ad1_select[] = { + SOC_DAPM_ENUM("AD 1 Select", dapm_enum_ad1_sel), +}; + +/* LineIn right - Mute */ +static const struct soc_enum enum_linr = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_linr_mux = + SOC_DAPM_ENUM_VIRT("LineIn Right", enum_linr); + +static const struct snd_kcontrol_new dapm_linr_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTLINR, 1, INVERT), +}; + +/* LineIn right - Mic 2 or LineIn Right selector */ +static const char * const enum_mic2lr_sel[] = {"Mic 2", "LineIn Right"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_mic2lr_sel, REG_ANACONF3, + REG_ANACONF3_LINRSEL, enum_mic2lr_sel); +static const struct snd_kcontrol_new dapm_mic2lr_select[] = { + SOC_DAPM_ENUM("Mic 2 or LINR Select", dapm_enum_mic2lr_sel), +}; + +/* LineIn right - AD2 - LineIn Right or DMic2 selector */ +static const char * const enum_ad2_sel[] = {"LineIn Right", "DMic 2"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad2_sel, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_AD2SEL, enum_ad2_sel); +static const struct snd_kcontrol_new dapm_ad2_select[] = { + SOC_DAPM_ENUM("AD 2 Select", dapm_enum_ad2_sel), +}; + +/* AD1 to DA1 (IHF Left) Switch */ +static const struct soc_enum enum_ad1_to_ihf_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_ad1_to_ihf_left_mux = + SOC_DAPM_ENUM_VIRT("AD1 to IHF Left", enum_ad1_to_ihf_left); + +/* AD2 to DA1 (IHF Left) Switch */ +static const struct soc_enum enum_ad2_to_ihf_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_ad2_to_ihf_right_mux = + SOC_DAPM_ENUM_VIRT("AD2 to IHF Right", enum_ad2_to_ihf_right); + +/* LineIn Left to Headset Left switch */ +static const struct soc_enum enum_linl_to_hs_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_linl_to_hs_left_mux = + SOC_DAPM_ENUM_VIRT("LineIn Left to Headset Left", enum_linl_to_hs_left); + +/* LineIn Right to Headset Right switch */ +static const struct soc_enum enum_linr_to_hs_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_linr_to_hs_right_mux = + SOC_DAPM_ENUM_VIRT("LineIn Right to Headset Right", enum_linr_to_hs_right); + + +/* DMic */ + +/* DMic 1 - Mute */ +static const struct soc_enum enum_dmic1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic1_mux = + SOC_DAPM_ENUM_VIRT("DMic 1", enum_dmic1); + +/* DMic 2 - Mute */ +static const struct soc_enum enum_dmic2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic2_mux = + SOC_DAPM_ENUM_VIRT("DMic 2", enum_dmic2); + +/* DMic 3 - Mute */ +static const struct soc_enum enum_dmic3 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic3_mux = + SOC_DAPM_ENUM_VIRT("DMic 3", enum_dmic3); + +/* DMic 4 - Mute */ +static const struct soc_enum enum_dmic4 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic4_mux = + SOC_DAPM_ENUM_VIRT("DMic 4", enum_dmic4); + +/* DMic 5 - Mute */ +static const struct soc_enum enum_dmic5 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic5_mux = + SOC_DAPM_ENUM_VIRT("DMic 5", enum_dmic5); + +/* DMic 6 - Mute */ +static const struct soc_enum enum_dmic6 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_dmic6_mux = + SOC_DAPM_ENUM_VIRT("DMic 6", enum_dmic6); + +/* ANC */ + +static const char * const enum_anc_in_sel[] = {"Mic 1 / DMic 6", "Mic 2 / DMic 5"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_in_sel, REG_DMICFILTCONF, + REG_DMICFILTCONF_ANCINSEL, enum_anc_in_sel); +static const struct snd_kcontrol_new dapm_anc_in_select[] = { + SOC_DAPM_ENUM("ANC Source", dapm_enum_anc_in_sel), +}; + +/* ANC - Enable/Disable */ +static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_enable, REG_ANCCONF1, + REG_ANCCONF1_ENANC, enum_dis_ena); +static const struct snd_kcontrol_new dapm_anc_enable[] = { + SOC_DAPM_ENUM("ANC", dapm_enum_anc_enable), +}; + +/* ANC to Earpiece - Mute */ +static const struct snd_kcontrol_new dapm_anc_ear_mute[] = { + SOC_DAPM_SINGLE("Playback Switch", REG_DIGMULTCONF1, + REG_DIGMULTCONF1_ANCSEL, 1, NORMAL), +}; + +/* Sidetone left */ + +/* Sidetone left - Input selector */ +static const char * const enum_stfir1_in_sel[] = { + "LineIn Left", "LineIn Right", "Mic 1", "Headset Left"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir1_in_sel, REG_DIGMULTCONF2, + REG_DIGMULTCONF2_FIRSID1SEL, enum_stfir1_in_sel); +static const struct snd_kcontrol_new dapm_stfir1_in_select[] = { + SOC_DAPM_ENUM("Sidetone Left Source", dapm_enum_stfir1_in_sel), +}; + +/* Sidetone right path */ + +/* Sidetone right - Input selector */ +static const char * const enum_stfir2_in_sel[] = { + "LineIn Right", "Mic 1", "DMic 4", "Headset Right"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir2_in_sel, REG_DIGMULTCONF2, + REG_DIGMULTCONF2_FIRSID2SEL, enum_stfir2_in_sel); +static const struct snd_kcontrol_new dapm_stfir2_in_select[] = { + SOC_DAPM_ENUM("Sidetone Right Source", dapm_enum_stfir2_in_sel), +}; + +/* Vibra */ + +/* Vibra 1 - Enable/Disable */ +static const struct soc_enum enum_vibra1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_vibra1_mux = + SOC_DAPM_ENUM_VIRT("Vibra 1", enum_vibra1); + +/* Vibra 2 - Enable/Disable */ +static const struct soc_enum enum_vibra2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena); +static const struct snd_kcontrol_new dapm_vibra2_mux = + SOC_DAPM_ENUM_VIRT("Vibra 2", enum_vibra2); + +static const char * const enum_pwm2vibx[] = {"Audio Path", "PWM Generator"}; + +static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib1, REG_PWMGENCONF1, + REG_PWMGENCONF1_PWMTOVIB1, enum_pwm2vibx); + +static const struct snd_kcontrol_new dapm_pwm2vib1[] = { + SOC_DAPM_ENUM("Vibra 1 Controller", dapm_enum_pwm2vib1), +}; + +static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib2, REG_PWMGENCONF1, + REG_PWMGENCONF1_PWMTOVIB2, enum_pwm2vibx); + +static const struct snd_kcontrol_new dapm_pwm2vib2[] = { + SOC_DAPM_ENUM("Vibra 2 Controller", dapm_enum_pwm2vib2), +}; + +/* Event-handlers - DAPM */ +static int linein_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + msleep(LINEIN_RAMP_DELAY); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + + /* DA/AD */ + + SND_SOC_DAPM_INPUT("ADC Input"), + SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DAC", "ab8500_0p", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("DAC Output"), + + SND_SOC_DAPM_AIF_IN("DA_IN1", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN2", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN3", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN4", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN5", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN6", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT1", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT2", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT3", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT4", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT57", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT68", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0), + + /* Headset path */ + + SND_SOC_DAPM_SUPPLY("Charge Pump", REG_ANACONF5, REG_ANACONF5_ENCPHS, + NORMAL, NULL, 0), + + SND_SOC_DAPM_DAC("DA1 Enable", "ab8500_0p", + REG_DAPATHENA, REG_DAPATHENA_ENDA1, 0), + SND_SOC_DAPM_DAC("DA2 Enable", "ab8500_0p", + REG_DAPATHENA, REG_DAPATHENA_ENDA2, 0), + + SND_SOC_DAPM_MUX("Headset Left", + SND_SOC_NOPM, 0, 0, &dapm_headset_left_mux), + SND_SOC_DAPM_MUX("Headset Right", + SND_SOC_NOPM, 0, 0, &dapm_headset_right_mux), + + SND_SOC_DAPM_PGA("HSL Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HSR Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_DAC("HSL DAC", "ab8500_0p", + REG_DAPATHCONF, REG_DAPATHCONF_ENDACHSL, 0), + SND_SOC_DAPM_DAC("HSR DAC", "ab8500_0p", + REG_DAPATHCONF, REG_DAPATHCONF_ENDACHSR, 0), + SND_SOC_DAPM_MIXER("HSL DAC Mute", REG_MUTECONF, REG_MUTECONF_MUTDACHSL, + INVERT, NULL, 0), + SND_SOC_DAPM_MIXER("HSR DAC Mute", REG_MUTECONF, REG_MUTECONF_MUTDACHSR, + INVERT, NULL, 0), + SND_SOC_DAPM_DAC("HSL DAC Driver", "ab8500_0p", + REG_ANACONF3, REG_ANACONF3_ENDRVHSL, 0), + SND_SOC_DAPM_DAC("HSR DAC Driver", "ab8500_0p", + REG_ANACONF3, REG_ANACONF3_ENDRVHSR, 0), + + SND_SOC_DAPM_MIXER("HSL Mute", REG_MUTECONF, REG_MUTECONF_MUTHSL, + INVERT, NULL, 0), + SND_SOC_DAPM_MIXER("HSR Mute", REG_MUTECONF, REG_MUTECONF_MUTHSR, + INVERT, NULL, 0), + SND_SOC_DAPM_MIXER("HSL Enable", REG_ANACONF4, REG_ANACONF4_ENHSL, + NORMAL, NULL, 0), + SND_SOC_DAPM_MIXER("HSR Enable", REG_ANACONF4, REG_ANACONF4_ENHSR, + NORMAL, NULL, 0), + SND_SOC_DAPM_PGA("HSL Gain", SND_SOC_NOPM, 0, + 0, NULL, 0), + SND_SOC_DAPM_PGA("HSR Gain", SND_SOC_NOPM, 0, + 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("HSR"), + + /* LineOut path */ + + SND_SOC_DAPM_MUX("LineOut Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_lineout_source), + + SND_SOC_DAPM_MIXER("LOL Enable", REG_ANACONF5, + REG_ANACONF5_ENLOL, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LOR Enable", REG_ANACONF5, + REG_ANACONF5_ENLOR, 0, NULL, 0), + + SND_SOC_DAPM_MUX("LineOut Left", + SND_SOC_NOPM, 0, 0, &dapm_lineout_left_mux), + + SND_SOC_DAPM_MUX("LineOut Right", + SND_SOC_NOPM, 0, 0, &dapm_lineout_right_mux), + + /* Earpiece path */ + + SND_SOC_DAPM_MUX("Earpiece or LineOut Mono Source", + SND_SOC_NOPM, 0, 0, dapm_ear_lineout_source), + + SND_SOC_DAPM_MIXER("EAR DAC", REG_DAPATHCONF, + REG_DAPATHCONF_ENDACEAR, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Earpiece", SND_SOC_NOPM, 0, 0, &dapm_ear_mux), + + SND_SOC_DAPM_MIXER("EAR Mute", REG_MUTECONF, + REG_MUTECONF_MUTEAR, 1, NULL, 0), + + SND_SOC_DAPM_MIXER("EAR Enable", REG_ANACONF4, + REG_ANACONF4_ENEAR, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("EAR"), + + /* Handsfree path */ + + SND_SOC_DAPM_MIXER("DA3 Channel Gain", REG_DAPATHENA, + REG_DAPATHENA_ENDA3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DA4 Channel Gain", REG_DAPATHENA, + REG_DAPATHENA_ENDA4, 0, NULL, 0), + + SND_SOC_DAPM_MUX("IHF Left Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_ihfl_select), + SND_SOC_DAPM_MUX("IHF Right Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_ihfr_select), + + SND_SOC_DAPM_MUX("IHF Left", SND_SOC_NOPM, 0, 0, &dapm_ihf_left_mux), + SND_SOC_DAPM_MUX("IHF Right", SND_SOC_NOPM, 0, 0, &dapm_ihf_right_mux), + + SND_SOC_DAPM_MUX("IHF or LineOut Select", SND_SOC_NOPM, + 0, 0, &dapm_ihf_or_lineout_select_mux), + + SND_SOC_DAPM_MIXER("IHFL DAC", REG_DAPATHCONF, + REG_DAPATHCONF_ENDACHFL, 0, NULL, 0), + SND_SOC_DAPM_MIXER("IHFR DAC", REG_DAPATHCONF, + REG_DAPATHCONF_ENDACHFR, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("DA4 or ANC path to HfR", REG_DIGMULTCONF2, + REG_DIGMULTCONF2_DATOHFREN, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DA3 or ANC path to HfL", REG_DIGMULTCONF2, + REG_DIGMULTCONF2_DATOHFLEN, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("IHFL Enable", REG_ANACONF4, + REG_ANACONF4_ENHFL, 0, NULL, 0), + SND_SOC_DAPM_MIXER("IHFR Enable", REG_ANACONF4, + REG_ANACONF4_ENHFR, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("IHFL"), + SND_SOC_DAPM_OUTPUT("IHFR"), + + /* Vibrator path */ + + SND_SOC_DAPM_MUX("Vibra 1", SND_SOC_NOPM, 0, 0, &dapm_vibra1_mux), + SND_SOC_DAPM_MUX("Vibra 2", SND_SOC_NOPM, 0, 0, &dapm_vibra2_mux), + SND_SOC_DAPM_MIXER("DA5 Channel Gain", REG_DAPATHENA, + REG_DAPATHENA_ENDA5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DA6 Channel Gain", REG_DAPATHENA, + REG_DAPATHENA_ENDA6, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("VIB1 DAC", REG_DAPATHCONF, + REG_DAPATHCONF_ENDACVIB1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("VIB2 DAC", REG_DAPATHCONF, + REG_DAPATHCONF_ENDACVIB2, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("PWMGEN1"), + SND_SOC_DAPM_INPUT("PWMGEN2"), + + SND_SOC_DAPM_MUX("Vibra 1 Controller Playback Route", + SND_SOC_NOPM, 0, 0, dapm_pwm2vib1), + SND_SOC_DAPM_MUX("Vibra 2 Controller Playback Route", + SND_SOC_NOPM, 0, 0, dapm_pwm2vib2), + + SND_SOC_DAPM_MIXER("VIB1 Enable", REG_ANACONF4, + REG_ANACONF4_ENVIB1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("VIB2 Enable", REG_ANACONF4, + REG_ANACONF4_ENVIB2, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("VIB1"), + SND_SOC_DAPM_OUTPUT("VIB2"), + + /* LineIn & Microphone 2 path */ + + SND_SOC_DAPM_INPUT("LINL"), + SND_SOC_DAPM_INPUT("LINR"), + SND_SOC_DAPM_INPUT("MIC2 Input"), + + + SND_SOC_DAPM_MUX("LineIn Left", SND_SOC_NOPM, 0, 0, &dapm_linl_mux), + SND_SOC_DAPM_MIXER("LINL Mute", REG_ANACONF2, + REG_ANACONF2_MUTLINL, INVERT, NULL, 0), + + SND_SOC_DAPM_MUX("LineIn Right", SND_SOC_NOPM, 0, 0, &dapm_linr_mux), + SND_SOC_DAPM_MIXER("LINR Mute", REG_ANACONF2, + REG_ANACONF2_MUTLINR, INVERT, NULL, 0), + + SND_SOC_DAPM_MUX("Mic 2", SND_SOC_NOPM, 0, 0, &dapm_mic2_mux), + SND_SOC_DAPM_MIXER("MIC2 Mute", REG_ANACONF2, + REG_ANACONF2_MUTMIC2, INVERT, NULL, 0), + + SND_SOC_DAPM_MIXER_E("LINL Enable", REG_ANACONF2, + REG_ANACONF2_ENLINL, 0, NULL, 0, + linein_enable_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER_E("LINR Enable", REG_ANACONF2, + REG_ANACONF2_ENLINR, 0, NULL, 0, + linein_enable_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("MIC2 Enable", REG_ANACONF2, + REG_ANACONF2_ENMIC2, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Mic 2 or LINR Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_mic2lr_select), + + SND_SOC_DAPM_MIXER("LINL ADC", REG_ANACONF3, + REG_ANACONF3_ENADCLINL, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LINR ADC", REG_ANACONF3, + REG_ANACONF3_ENADCLINR, 0, NULL, 0), + + SND_SOC_DAPM_MUX("AD 1 Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_ad1_select), + SND_SOC_DAPM_MUX("AD 2 Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_ad2_select), + + SND_SOC_DAPM_MIXER("AD1 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("AD2 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("AD12 Enable", REG_ADPATHENA, + REG_ADPATHENA_ENAD12, 0, NULL, 0), + + /* Microphone 1 path */ + + SND_SOC_DAPM_INPUT("MIC1A Input"), + SND_SOC_DAPM_INPUT("MIC1B Input"), + + SND_SOC_DAPM_MUX("Mic 1A or 1B Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_mic1ab_select), + + SND_SOC_DAPM_MUX("Mic 1", SND_SOC_NOPM, 0, 0, &dapm_mic1_mux), + + SND_SOC_DAPM_MIXER("MIC1 Mute", REG_ANACONF2, + REG_ANACONF2_MUTMIC1, INVERT, NULL, 0), + + SND_SOC_DAPM_MIXER("MIC1 Enable", REG_ANACONF2, + REG_ANACONF2_ENMIC1, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("MIC1 ADC", REG_ANACONF3, + REG_ANACONF3_ENADCMIC, 0, NULL, 0), + + SND_SOC_DAPM_MUX("AD 3 Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_ad3_select), + + SND_SOC_DAPM_MIXER("AD3 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("AD3 Enable", REG_ADPATHENA, + REG_ADPATHENA_ENAD34, 0, NULL, 0), + + /* HD Capture path */ + + SND_SOC_DAPM_MUX("AD 5 Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_ad5_select), + SND_SOC_DAPM_MUX("AD 6 Select Capture Route", + SND_SOC_NOPM, 0, 0, dapm_ad6_select), + + SND_SOC_DAPM_MIXER("AD5 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("AD6 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("AD57 Enable", REG_ADPATHENA, + REG_ADPATHENA_ENAD5768, 0, NULL, 0), + SND_SOC_DAPM_MIXER("AD68 Enable", REG_ADPATHENA, + REG_ADPATHENA_ENAD5768, 0, NULL, 0), + + /* Digital Microphone path */ + + SND_SOC_DAPM_INPUT("DMIC Input"), + + SND_SOC_DAPM_MUX("DMic 1", SND_SOC_NOPM, 0, 0, &dapm_dmic1_mux), + SND_SOC_DAPM_MUX("DMic 2", SND_SOC_NOPM, 0, 0, &dapm_dmic2_mux), + SND_SOC_DAPM_MUX("DMic 3", SND_SOC_NOPM, 0, 0, &dapm_dmic3_mux), + SND_SOC_DAPM_MUX("DMic 4", SND_SOC_NOPM, 0, 0, &dapm_dmic4_mux), + SND_SOC_DAPM_MUX("DMic 5", SND_SOC_NOPM, 0, 0, &dapm_dmic5_mux), + SND_SOC_DAPM_MUX("DMic 6", SND_SOC_NOPM, 0, 0, &dapm_dmic6_mux), + + SND_SOC_DAPM_MIXER("DMIC1 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DMIC2 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DMIC3 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DMIC4 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DMIC5 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DMIC6 Mute", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC6, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("AD4 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("AD4 Enable", REG_ADPATHENA, + REG_ADPATHENA_ENAD34, 0, NULL, 0), + + /* LineIn to Bypass path */ + + SND_SOC_DAPM_MIXER("LINL to HSL Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LINR to HSR Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("LineIn Left to Headset Left", SND_SOC_NOPM, 0, 0, &dapm_linl_to_hs_left_mux), + SND_SOC_DAPM_MUX("LineIn Right to Headset Right", SND_SOC_NOPM, 0, 0, &dapm_linr_to_hs_right_mux), + + /* LineIn to Speaker */ + + SND_SOC_DAPM_MUX("AD1 to IHF Left", SND_SOC_NOPM, 0, 0, &dapm_ad1_to_ihf_left_mux), + SND_SOC_DAPM_MUX("AD2 to IHF Right", SND_SOC_NOPM, 0, 0, &dapm_ad2_to_ihf_right_mux), + + /* Acoustical Noise Cancellation path */ + + SND_SOC_DAPM_MUX("ANC Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_anc_in_select), + + SND_SOC_DAPM_MUX("ANC Playback Switch", + SND_SOC_NOPM, 0, 0, dapm_anc_enable), + + SND_SOC_DAPM_SWITCH("ANC to Earpiece", + SND_SOC_NOPM, 0, 0, dapm_anc_ear_mute), + + /* Sidetone Filter path */ + + SND_SOC_DAPM_MUX("Sidetone Left Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_stfir1_in_select), + SND_SOC_DAPM_MUX("Sidetone Right Source Playback Route", + SND_SOC_NOPM, 0, 0, dapm_stfir2_in_select), + + SND_SOC_DAPM_MIXER("STFIR1 Control", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("STFIR2 Control", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("STFIR1 Gain", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("STFIR2 Gain", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +/* DAPM-routes */ + +static const struct snd_soc_dapm_route dapm_routes[] = { + /* AD/DA */ + {"ADC", NULL, "ADC Input"}, + {"DAC Output", NULL, "DAC"}, + + /* Powerup charge pump if DA1/2 is in use */ + {"HSL Mute", NULL, "Charge Pump"}, + {"HSR Mute", NULL, "Charge Pump"}, + + /* Headset path */ + + {"DA1 Enable", NULL, "DA_IN1"}, + {"DA2 Enable", NULL, "DA_IN2"}, + + {"HSL Digital Gain", NULL, "DA1 Enable"}, + {"HSR Digital Gain", NULL, "DA2 Enable"}, + + {"HSL DAC", NULL, "HSL Digital Gain"}, + {"HSR DAC", NULL, "HSR Digital Gain"}, + + {"HSL DAC Mute", NULL, "HSL DAC"}, + {"HSR DAC Mute", NULL, "HSR DAC"}, + + {"HSL DAC Driver", NULL, "HSL DAC Mute"}, + {"HSR DAC Driver", NULL, "HSR DAC Mute"}, + + {"HSL Mute", NULL, "HSL DAC Driver"}, + {"HSR Mute", NULL, "HSR DAC Driver"}, + + {"Headset Left", "Enabled", "HSL Mute"}, + {"Headset Right", "Enabled", "HSR Mute"}, + + {"HSL Enable", NULL, "Headset Left"}, + {"HSR Enable", NULL, "Headset Right"}, + + {"HSL Gain", NULL, "HSL Enable"}, + {"HSR Gain", NULL, "HSR Enable"}, + + {"HSL", NULL, "HSL Gain"}, + {"HSR", NULL, "HSR Gain"}, + + /* IHF or LineOut path */ + + {"DA3 Channel Gain", NULL, "DA_IN3"}, + {"DA4 Channel Gain", NULL, "DA_IN4"}, + + {"IHF Left Source Playback Route", "Audio Path", "DA3 Channel Gain"}, + {"IHF Right Source Playback Route", "Audio Path", "DA4 Channel Gain"}, + + {"DA3 or ANC path to HfL", NULL, "IHF Left Source Playback Route"}, + {"DA4 or ANC path to HfR", NULL, "IHF Right Source Playback Route"}, + + /* IHF path */ + + {"IHF Left", "Enabled", "DA3 or ANC path to HfL"}, + {"IHF Right", "Enabled", "DA4 or ANC path to HfR"}, + + {"IHFL DAC", NULL, "IHF Left"}, + {"IHFR DAC", NULL, "IHF Right"}, + + {"IHFL Enable", NULL, "IHFL DAC"}, + {"IHFR Enable", NULL, "IHFR DAC"}, + + {"IHF or LineOut Select", "IHF", "IHFL Enable"}, + {"IHF or LineOut Select", "IHF", "IHFR Enable"}, + + /* Earpiece path */ + + {"Earpiece or LineOut Mono Source", "Headset Left", "HSL Digital Gain"}, + {"Earpiece or LineOut Mono Source", "IHF Left", "DA3 or ANC path to HfL"}, + + {"EAR DAC", NULL, "Earpiece or LineOut Mono Source"}, + + {"Earpiece", "Enabled", "EAR DAC"}, + + {"EAR Mute", NULL, "Earpiece"}, + + {"EAR Enable", NULL, "EAR Mute"}, + + {"EAR", NULL, "EAR Enable"}, + + /* LineOut path stereo */ + + {"LineOut Source Playback Route", "Stereo Path", "HSL DAC Driver"}, + {"LineOut Source Playback Route", "Stereo Path", "HSR DAC Driver"}, + + /* LineOut path mono */ + + {"LineOut Source Playback Route", "Mono Path", "EAR DAC"}, + + /* LineOut path */ + + {"LineOut Left", "Enabled", "LineOut Source Playback Route"}, + {"LineOut Right", "Enabled", "LineOut Source Playback Route"}, + + {"LOL Enable", NULL, "LineOut Left"}, + {"LOR Enable", NULL, "LineOut Right"}, + + {"IHF or LineOut Select", "LineOut", "LOL Enable"}, + {"IHF or LineOut Select", "LineOut", "LOR Enable"}, + + /* IHF path */ + + {"IHFL", NULL, "IHF or LineOut Select"}, + {"IHFR", NULL, "IHF or LineOut Select"}, + + /* Vibrator path */ + + {"DA5 Channel Gain", NULL, "DA_IN5"}, + {"DA6 Channel Gain", NULL, "DA_IN6"}, + + {"VIB1 DAC", NULL, "DA5 Channel Gain"}, + {"VIB2 DAC", NULL, "DA6 Channel Gain"}, + + {"Vibra 1 Controller Playback Route", "Audio Path", "VIB1 DAC"}, + {"Vibra 2 Controller Playback Route", "Audio Path", "VIB2 DAC"}, + {"Vibra 1 Controller Playback Route", "PWM Generator", "PWMGEN1"}, + {"Vibra 2 Controller Playback Route", "PWM Generator", "PWMGEN2"}, + + {"Vibra 1", "Enabled", "Vibra 1 Controller Playback Route"}, + {"Vibra 2", "Enabled", "Vibra 2 Controller Playback Route"}, + + {"VIB1 Enable", NULL, "Vibra 1"}, + {"VIB2 Enable", NULL, "Vibra 2"}, + + {"VIB1", NULL, "VIB1 Enable"}, + {"VIB2", NULL, "VIB2 Enable"}, + + /* LineIn & Microphone 2 path */ + + {"LineIn Left", "Enabled", "LINL"}, + {"LineIn Right", "Enabled", "LINR"}, + + {"LINL Mute", NULL, "LineIn Left"}, + {"LINR Mute", NULL, "LineIn Right"}, + + {"Mic 2", "Enabled", "MIC2 Input"}, + + {"MIC2 Mute", NULL, "Mic 2"}, + + {"LINL Enable", NULL, "LINL Mute"}, + {"LINR Enable", NULL, "LINR Mute"}, + {"MIC2 Enable", NULL, "MIC2 Mute"}, + + {"Mic 2 or LINR Select Capture Route", "LineIn Right", "LINR Enable"}, + {"Mic 2 or LINR Select Capture Route", "Mic 2", "MIC2 Enable"}, + + {"LINL ADC", NULL, "LINL Enable"}, + {"LINR ADC", NULL, "Mic 2 or LINR Select Capture Route"}, + + {"AD 1 Select Capture Route", "LineIn Left", "LINL ADC"}, + {"AD 2 Select Capture Route", "LineIn Right", "LINR ADC"}, + + {"AD1 Channel Gain", NULL, "AD 1 Select Capture Route"}, + {"AD2 Channel Gain", NULL, "AD 2 Select Capture Route"}, + + {"AD12 Enable", NULL, "AD1 Channel Gain"}, + {"AD12 Enable", NULL, "AD2 Channel Gain"}, + + {"AD_OUT1", NULL, "AD12 Enable"}, + {"AD_OUT2", NULL, "AD12 Enable"}, + + /* Microphone 1 path */ + + {"Mic 1A or 1B Select Capture Route", "Mic 1A", "MIC1A Input"}, + {"Mic 1A or 1B Select Capture Route", "Mic 1B", "MIC1B Input"}, + + {"Mic 1", "Enabled", "Mic 1A or 1B Select Capture Route"}, + + {"MIC1 Mute", NULL, "Mic 1"}, + + {"MIC1 Enable", NULL, "MIC1 Mute"}, + + {"MIC1 ADC", NULL, "MIC1 Enable"}, + + {"AD 3 Select Capture Route", "Mic 1", "MIC1 ADC"}, + + {"AD3 Channel Gain", NULL, "AD 3 Select Capture Route"}, + + {"AD3 Enable", NULL, "AD3 Channel Gain"}, + + {"AD_OUT3", NULL, "AD3 Enable"}, + + /* HD Capture path */ + + {"AD 5 Select Capture Route", "Mic 2", "LINR ADC"}, + {"AD 6 Select Capture Route", "Mic 1", "MIC1 ADC"}, + + {"AD5 Channel Gain", NULL, "AD 5 Select Capture Route"}, + {"AD6 Channel Gain", NULL, "AD 6 Select Capture Route"}, + + {"AD57 Enable", NULL, "AD5 Channel Gain"}, + {"AD68 Enable", NULL, "AD6 Channel Gain"}, + + {"AD_OUT57", NULL, "AD57 Enable"}, + {"AD_OUT68", NULL, "AD68 Enable"}, + + /* Digital Microphone path */ + + {"DMic 1", "Enabled", "DMIC Input"}, + {"DMic 2", "Enabled", "DMIC Input"}, + {"DMic 3", "Enabled", "DMIC Input"}, + {"DMic 4", "Enabled", "DMIC Input"}, + {"DMic 5", "Enabled", "DMIC Input"}, + {"DMic 6", "Enabled", "DMIC Input"}, + + {"DMIC1 Mute", NULL, "DMic 1"}, + {"DMIC2 Mute", NULL, "DMic 2"}, + {"DMIC3 Mute", NULL, "DMic 3"}, + {"DMIC4 Mute", NULL, "DMic 4"}, + {"DMIC5 Mute", NULL, "DMic 5"}, + {"DMIC6 Mute", NULL, "DMic 6"}, + + {"AD 1 Select Capture Route", "DMic 1", "DMIC1 Mute"}, + {"AD 2 Select Capture Route", "DMic 2", "DMIC2 Mute"}, + {"AD 3 Select Capture Route", "DMic 3", "DMIC3 Mute"}, + {"AD 5 Select Capture Route", "DMic 5", "DMIC5 Mute"}, + {"AD 6 Select Capture Route", "DMic 6", "DMIC6 Mute"}, + + {"AD4 Channel Gain", NULL, "DMIC4 Mute"}, + + {"AD4 Enable", NULL, "AD4 Channel Gain"}, + + {"AD_OUT4", NULL, "AD4 Enable"}, + + /* LineIn to Headset Bypass path */ + + {"LINL to HSL Gain", NULL, "LINL Enable"}, + {"LINR to HSR Gain", NULL, "LINR Enable"}, + + {"LineIn Left to Headset Left", "Enabled", "LINL to HSL Gain"}, + {"LineIn Right to Headset Right", "Enabled", "LINR to HSR Gain"}, + + {"HSL DAC Driver", NULL, "LineIn Left to Headset Left"}, + {"HSR DAC Driver", NULL, "LineIn Right to Headset Right"}, + + /* LineIn to Speaker path */ + + {"AD1 to IHF Left", "Enabled", "AD 1 Select Capture Route"}, + {"AD2 to IHF Right", "Enabled", "AD 2 Select Capture Route"}, + + {"IHFL DAC", NULL, "AD1 to IHF Left"}, + {"IHFR DAC", NULL, "AD2 to IHF Right"}, + + /* Acoustical Noise Cancellation path */ + + {"ANC Source Playback Route", "Mic 2 / DMic 5", "AD5 Channel Gain"}, + {"ANC Source Playback Route", "Mic 1 / DMic 6", "AD6 Channel Gain"}, + + {"ANC Playback Switch", "Enabled", "ANC Source Playback Route"}, + + {"IHF Left Source Playback Route", "ANC", "ANC Playback Switch"}, + {"IHF Right Source Playback Route", "ANC", "ANC Playback Switch"}, + {"ANC to Earpiece", "Playback Switch", "ANC Playback Switch"}, + + {"HSL Digital Gain", NULL, "ANC to Earpiece"}, + + /* Sidetone Filter path */ + + {"Sidetone Left Source Playback Route", "LineIn Left", "AD12 Enable"}, + {"Sidetone Left Source Playback Route", "LineIn Right", "AD12 Enable"}, + {"Sidetone Left Source Playback Route", "Mic 1", "AD3 Enable"}, + {"Sidetone Left Source Playback Route", "Headset Left", "DA_IN1"}, + {"Sidetone Right Source Playback Route", "LineIn Right", "AD12 Enable"}, + {"Sidetone Right Source Playback Route", "Mic 1", "AD3 Enable"}, + {"Sidetone Right Source Playback Route", "DMic 4", "AD4 Enable"}, + {"Sidetone Right Source Playback Route", "Headset Right", "DA_IN2"}, + + {"STFIR1 Control", NULL, "Sidetone Left Source Playback Route"}, + {"STFIR2 Control", NULL, "Sidetone Right Source Playback Route"}, + + {"STFIR1 Gain", NULL, "STFIR1 Control"}, + {"STFIR2 Gain", NULL, "STFIR2 Control"}, + + {"DA1 Enable", NULL, "STFIR1 Gain"}, + {"DA2 Enable", NULL, "STFIR2 Gain"}, +}; + +/* Controls - Non-DAPM ASoC */ + +/* from -31 to 31 dB in 1 dB steps (mute instead of -32 dB) */ +static DECLARE_TLV_DB_SCALE(adx_dig_gain_tlv, -3200, 100, 1); + +/* from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) */ +static DECLARE_TLV_DB_SCALE(dax_dig_gain_tlv, -6300, 100, 1); + +/* from 0 to 8 dB in 1 dB steps (mute instead of -1 dB) */ +static DECLARE_TLV_DB_SCALE(hs_ear_dig_gain_tlv, -100, 100, 1); + +/* from -30 to 0 dB in 1 dB steps (mute instead of -31 dB) */ +static DECLARE_TLV_DB_SCALE(stfir_dig_gain_tlv, -3100, 100, 1); + +/* from -32 to -20 dB in 4 dB steps / from -18 to 2 dB in 2 dB steps */ +static const unsigned int hs_gain_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 3, TLV_DB_SCALE_ITEM(-3200, 400, 0), + 4, 15, TLV_DB_SCALE_ITEM(-1800, 200, 0), +}; + +/* from 0 to 31 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); + +/* from -10 to 20 dB in 2 dB steps */ +static DECLARE_TLV_DB_SCALE(lin_gain_tlv, -1000, 200, 0); + +/* from -36 to 0 dB in 2 dB steps (mute instead of -38 dB) */ +static DECLARE_TLV_DB_SCALE(lin2hs_gain_tlv, -3800, 200, 1); + +static SOC_ENUM_SINGLE_DECL(soc_enum_hshpen, + REG_ANACONF1, REG_ANACONF1_HSHPEN, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_hslowpow, + REG_ANACONF1, REG_ANACONF1_HSLOWPOW, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow1, + REG_ANACONF1, REG_ANACONF1_DACLOWPOW1, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow0, + REG_ANACONF1, REG_ANACONF1_DACLOWPOW0, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_eardaclowpow, + REG_ANACONF1, REG_ANACONF1_EARDACLOWPOW, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_eardrvlowpow, + REG_ANACONF1, REG_ANACONF1_EARDRVLOWPOW, enum_dis_ena); + +static const char * const enum_earselcm[] = {"0.95V", "1.10V", "1.27V", "1.58V"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_earselcm, + REG_ANACONF1, REG_ANACONF1_EARSELCM, enum_earselcm); + +static const char * const enum_hsfadspeed[] = {"2ms", "0.5ms", "10.6ms", "5ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_hsfadspeed, + REG_DIGMICCONF, REG_DIGMICCONF_HSFADSPEED, enum_hsfadspeed); + +static const char * const enum_envdetthre[] = { + "250mV", "300mV", "350mV", "400mV", + "450mV", "500mV", "550mV", "600mV", + "650mV", "700mV", "750mV", "800mV", + "850mV", "900mV", "950mV", "1.00V" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_envdetcpen, + REG_SIGENVCONF, REG_SIGENVCONF_ENVDETCPEN, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_envdeththre, + REG_ENVCPCONF, REG_ENVCPCONF_ENVDETHTHRE, enum_envdetthre); +static SOC_ENUM_SINGLE_DECL(soc_enum_envdetlthre, + REG_ENVCPCONF, REG_ENVCPCONF_ENVDETLTHRE, enum_envdetthre); + +static const char * const enum_envdettime[] = { + "26.6us", "53.2us", "106us", "213us", + "426us", "851us", "1.70ms", "3.40ms", + "6.81ms", "13.6ms", "27.2ms", "54.5ms", + "109ms", "218ms", "436ms", "872ms" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_envdettime, + REG_SIGENVCONF, REG_SIGENVCONF_ENVDETTIME, enum_envdettime); + +static const char * const enum_ensemicx[] = {"Differential", "Single Ended"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic1, + REG_ANAGAIN1, REG_ANAGAINX_ENSEMICX, enum_ensemicx); +static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic2, + REG_ANAGAIN2, REG_ANAGAINX_ENSEMICX, enum_ensemicx); +static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic1, + REG_ANAGAIN1, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic2, + REG_ANAGAIN2, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena); + +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12nh, REG_ADFILTCONF, + REG_ADFILTCONF_AD1NH, REG_ADFILTCONF_AD2NH, enum_ena_dis); +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34nh, REG_ADFILTCONF, + REG_ADFILTCONF_AD3NH, REG_ADFILTCONF_AD4NH, enum_ena_dis); + +static const char * const enum_av_mode[] = {"Audio", "Voice"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12voice, REG_ADFILTCONF, + REG_ADFILTCONF_AD1VOICE, REG_ADFILTCONF_AD2VOICE, enum_av_mode); +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34voice, REG_ADFILTCONF, + REG_ADFILTCONF_AD3VOICE, REG_ADFILTCONF_AD4VOICE, enum_av_mode); + +static SOC_ENUM_SINGLE_DECL(soc_enum_da12voice, + REG_DASLOTCONF1, REG_DASLOTCONF1_DA12VOICE, enum_av_mode); +static SOC_ENUM_SINGLE_DECL(soc_enum_da34voice, + REG_DASLOTCONF3, REG_DASLOTCONF3_DA34VOICE, enum_av_mode); +static SOC_ENUM_SINGLE_DECL(soc_enum_da56voice, + REG_DASLOTCONF5, REG_DASLOTCONF5_DA56VOICE, enum_av_mode); + +static SOC_ENUM_SINGLE_DECL(soc_enum_swapda12_34, + REG_DASLOTCONF1, REG_DASLOTCONF1_SWAPDA12_34, enum_dis_ena); + +static SOC_ENUM_DOUBLE_DECL(soc_enum_vib12swap, REG_CLASSDCONF1, + REG_CLASSDCONF1_VIB1SWAPEN, REG_CLASSDCONF1_VIB2SWAPEN, enum_dis_ena); +static SOC_ENUM_DOUBLE_DECL(soc_enum_hflrswap, REG_CLASSDCONF1, + REG_CLASSDCONF1_HFLSWAPEN, REG_CLASSDCONF1_HFRSWAPEN, enum_dis_ena); + +static SOC_ENUM_DOUBLE_DECL(soc_enum_fir01byp, REG_CLASSDCONF2, + REG_CLASSDCONF2_FIRBYP0, REG_CLASSDCONF2_FIRBYP1, enum_dis_ena); +static SOC_ENUM_DOUBLE_DECL(soc_enum_fir23byp, REG_CLASSDCONF2, + REG_CLASSDCONF2_FIRBYP2, REG_CLASSDCONF2_FIRBYP3, enum_dis_ena); +static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol01, REG_CLASSDCONF2, + REG_CLASSDCONF2_HIGHVOLEN0, REG_CLASSDCONF2_HIGHVOLEN1, enum_dis_ena); +static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol23, REG_CLASSDCONF2, + REG_CLASSDCONF2_HIGHVOLEN2, REG_CLASSDCONF2_HIGHVOLEN3, enum_dis_ena); + +static const char * const enum_sinc53[] = {"Sinc 5", "Sinc 3"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic12sinc, REG_DMICFILTCONF, + REG_DMICFILTCONF_DMIC1SINC3, REG_DMICFILTCONF_DMIC2SINC3, enum_sinc53); +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic34sinc, REG_DMICFILTCONF, + REG_DMICFILTCONF_DMIC3SINC3, REG_DMICFILTCONF_DMIC4SINC3, enum_sinc53); +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic56sinc, REG_DMICFILTCONF, + REG_DMICFILTCONF_DMIC5SINC3, REG_DMICFILTCONF_DMIC6SINC3, enum_sinc53); + +static const char * const enum_da2hslr[] = {"Sidetone", "Audio Path"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_da2hslr, REG_DIGMULTCONF1, + REG_DIGMULTCONF1_DATOHSLEN, REG_DIGMULTCONF1_DATOHSREN, enum_da2hslr); + +static const char * const enum_sinc31[] = {"Sinc 3", "Sinc 1"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_hsesinc, + REG_HSLEARDIGGAIN, REG_HSLEARDIGGAIN_HSSINC1, enum_sinc31); + +static const char * const enum_fadespeed[] = {"1ms", "4ms", "8ms", "16ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_fadespeed, + REG_HSRDIGGAIN, REG_HSRDIGGAIN_FADESPEED, enum_fadespeed); + +/* Digital interface - Clocks */ +static SOC_ENUM_SINGLE_DECL(soc_enum_mastgen, + REG_DIGIFCONF1, REG_DIGIFCONF1_ENMASTGEN, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_fsbitclk0, + REG_DIGIFCONF1, REG_DIGIFCONF1_ENFSBITCLK0, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_fsbitclk1, + REG_DIGIFCONF1, REG_DIGIFCONF1_ENFSBITCLK1, enum_dis_ena); + +/* Digital interface - DA from slot mapping */ +static const char * const enum_da_from_slot_map[] = {"SLOT0", + "SLOT1", + "SLOT2", + "SLOT3", + "SLOT4", + "SLOT5", + "SLOT6", + "SLOT7", + "SLOT8", + "SLOT9", + "SLOT10", + "SLOT11", + "SLOT12", + "SLOT13", + "SLOT14", + "SLOT15", + "SLOT16", + "SLOT17", + "SLOT18", + "SLOT19", + "SLOT20", + "SLOT21", + "SLOT22", + "SLOT23", + "SLOT24", + "SLOT25", + "SLOT26", + "SLOT27", + "SLOT28", + "SLOT29", + "SLOT30", + "SLOT31"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_da1slotmap, + REG_DASLOTCONF1, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da2slotmap, + REG_DASLOTCONF2, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da3slotmap, + REG_DASLOTCONF3, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da4slotmap, + REG_DASLOTCONF4, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da5slotmap, + REG_DASLOTCONF5, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da6slotmap, + REG_DASLOTCONF6, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da7slotmap, + REG_DASLOTCONF7, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da8slotmap, + REG_DASLOTCONF8, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map); + +/* Digital interface - AD to slot mapping */ +static const char * const enum_ad_to_slot_map[] = {"AD_OUT1", + "AD_OUT2", + "AD_OUT3", + "AD_OUT4", + "AD_OUT5", + "AD_OUT6", + "AD_OUT7", + "AD_OUT8", + "zeroes", + "tristate"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot0map, + REG_ADSLOTSEL1, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot1map, + REG_ADSLOTSEL1, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot2map, + REG_ADSLOTSEL2, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot3map, + REG_ADSLOTSEL2, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot4map, + REG_ADSLOTSEL3, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot5map, + REG_ADSLOTSEL3, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot6map, + REG_ADSLOTSEL4, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot7map, + REG_ADSLOTSEL4, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot8map, + REG_ADSLOTSEL5, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot9map, + REG_ADSLOTSEL5, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot10map, + REG_ADSLOTSEL6, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot11map, + REG_ADSLOTSEL6, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot12map, + REG_ADSLOTSEL7, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot13map, + REG_ADSLOTSEL7, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot14map, + REG_ADSLOTSEL8, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot15map, + REG_ADSLOTSEL8, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot16map, + REG_ADSLOTSEL9, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot17map, + REG_ADSLOTSEL9, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot18map, + REG_ADSLOTSEL10, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot19map, + REG_ADSLOTSEL10, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot20map, + REG_ADSLOTSEL11, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot21map, + REG_ADSLOTSEL11, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot22map, + REG_ADSLOTSEL12, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot23map, + REG_ADSLOTSEL12, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot24map, + REG_ADSLOTSEL13, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot25map, + REG_ADSLOTSEL13, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot26map, + REG_ADSLOTSEL14, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot27map, + REG_ADSLOTSEL14, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot28map, + REG_ADSLOTSEL15, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot29map, + REG_ADSLOTSEL15, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot30map, + REG_ADSLOTSEL16, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot31map, + REG_ADSLOTSEL16, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map); + +/* Digital interface - Digital loopback */ +static SOC_ENUM_SINGLE_DECL(soc_enum_ad1loop, + REG_DASLOTCONF1, REG_DASLOTCONF1_DAI7TOADO1, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad2loop, + REG_DASLOTCONF2, REG_DASLOTCONF2_DAI8TOADO2, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad3loop, + REG_DASLOTCONF3, REG_DASLOTCONF3_DAI7TOADO3, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad4loop, + REG_DASLOTCONF4, REG_DASLOTCONF4_DAI8TOADO4, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad5loop, + REG_DASLOTCONF5, REG_DASLOTCONF5_DAI7TOADO5, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad6loop, + REG_DASLOTCONF6, REG_DASLOTCONF6_DAI8TOADO6, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad7loop, + REG_DASLOTCONF7, REG_DASLOTCONF7_DAI8TOADO7, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_ad8loop, + REG_DASLOTCONF8, REG_DASLOTCONF8_DAI7TOADO8, enum_dis_ena); + +/* Digital interface - Burst mode */ +static SOC_ENUM_SINGLE_DECL(soc_enum_if0fifoen, + REG_DIGIFCONF3, REG_DIGIFCONF3_IF0BFIFOEN, enum_dis_ena); +static const char * const enum_mask[] = {"Unmasked", "Masked"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomask, + REG_FIFOCONF1, REG_FIFOCONF1_BFIFOMASK, enum_mask); +static const char * const enum_bitclk0[] = {"38_4_MHz", "19_2_MHz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifo19m2, + REG_FIFOCONF1, REG_FIFOCONF1_BFIFO19M2, enum_bitclk0); +static const char * const enum_slavemaster[] = {"Slave", "Master"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + REG_FIFOCONF3, REG_FIFOCONF3_BFIFOMAST_SHIFT, enum_slavemaster); +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifoint, + REG_FIFOCONF3, REG_FIFOCONF3_BFIFORUN_SHIFT, enum_dis_ena); + +/* TODO: move to DAPM */ +static SOC_ENUM_SINGLE_DECL(soc_enum_enfirsids, + REG_SIDFIRCONF, REG_SIDFIRCONF_ENFIRSIDS, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_parlhf, + REG_CLASSDCONF1, REG_CLASSDCONF1_PARLHF, enum_dis_ena); +static SOC_ENUM_SINGLE_DECL(soc_enum_parlvib, + REG_CLASSDCONF1, REG_CLASSDCONF1_PARLVIB, enum_dis_ena); +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_applysidetone, enum_rdy_apl); + +static struct snd_kcontrol_new ab8500_snd_controls[] = { + SOC_ENUM("Headset High Pass Playback Switch", soc_enum_hshpen), + SOC_ENUM("Headset Low Power Playback Switch", soc_enum_hslowpow), + SOC_ENUM("Headset DAC Low Power Playback Switch", soc_enum_daclowpow1), + SOC_ENUM("Headset DAC Drv Low Power Playback Switch", + soc_enum_daclowpow0), + SOC_ENUM("Earpiece DAC Low Power Playback Switch", + soc_enum_eardaclowpow), + SOC_ENUM("Earpiece DAC Drv Low Power Playback Switch", + soc_enum_eardrvlowpow), + SOC_ENUM("Earpiece Common Mode Playback Switch", soc_enum_earselcm), + + SOC_ENUM("Headset Fade Speed Playback Switch", soc_enum_hsfadspeed), + + SOC_ENUM("Charge Pump High Threshold For Low Voltage", + soc_enum_envdeththre), + SOC_ENUM("Charge Pump Low Threshold For Low Voltage", + soc_enum_envdetlthre), + SOC_ENUM("Charge Pump Envelope Detection", soc_enum_envdetcpen), + SOC_ENUM("Charge Pump Envelope Detection Decay Time", + soc_enum_envdettime), + + SOC_ENUM("Mic 1 Type Capture Switch", soc_enum_ensemic1), + SOC_ENUM("Mic 2 Type Capture Switch", soc_enum_ensemic2), + SOC_ENUM("Mic 1 Low Power Capture Switch", soc_enum_lowpowmic1), + SOC_ENUM("Mic 2 Low Power Capture Switch", soc_enum_lowpowmic2), + + SOC_ENUM("LineIn High Pass Capture Switch", soc_enum_ad12nh), + SOC_ENUM("Mic High Pass Capture Switch", soc_enum_ad34nh), + SOC_ENUM("LineIn Mode Capture Switch", soc_enum_ad12voice), + SOC_ENUM("Mic Mode Capture Switch", soc_enum_ad34voice), + + SOC_ENUM("Headset Mode Playback Switch", soc_enum_da12voice), + SOC_ENUM("IHF Mode Playback Switch", soc_enum_da34voice), + SOC_ENUM("Vibra Mode Playback Switch", soc_enum_da56voice), + + SOC_ENUM("IHF and Headset Swap Playback Switch", soc_enum_swapda12_34), + + SOC_ENUM("IHF Low EMI Mode Playback Switch", soc_enum_hflrswap), + SOC_ENUM("Vibra Low EMI Mode Playback Switch", soc_enum_vib12swap), + + SOC_ENUM("IHF FIR Bypass Playback Switch", soc_enum_fir01byp), + SOC_ENUM("Vibra FIR Bypass Playback Switch", soc_enum_fir23byp), + + /* TODO: Cannot be changed on the fly with digital channel enabled. */ + SOC_ENUM("IHF High Volume Playback Switch", soc_enum_highvol01), + SOC_ENUM("Vibra High Volume Playback Switch", soc_enum_highvol23), + + SOC_SINGLE("ClassD High Pass Gain Playback Volume", + REG_CLASSDCONF3, REG_CLASSDCONF3_DITHHPGAIN, + REG_CLASSDCONF3_DITHHPGAIN_MAX, NORMAL), + SOC_SINGLE("ClassD White Gain Playback Volume", + REG_CLASSDCONF3, REG_CLASSDCONF3_DITHWGAIN, + REG_CLASSDCONF3_DITHWGAIN_MAX, NORMAL), + + SOC_ENUM("LineIn Filter Capture Switch", soc_enum_dmic12sinc), + SOC_ENUM("Mic Filter Capture Switch", soc_enum_dmic34sinc), + SOC_ENUM("HD Mic Filter Capture Switch", soc_enum_dmic56sinc), + + SOC_ENUM("Headset Source Playback Route", soc_enum_da2hslr), + + /* TODO: Cannot be changed on the fly with digital channel enabled. */ + SOC_ENUM("Headset Filter Playback Switch", soc_enum_hsesinc), + + SOC_ENUM("Digital Gain Fade Speed Switch", soc_enum_fadespeed), + + SOC_DOUBLE_R("Vibra PWM Duty Cycle N Playback Volume", + REG_PWMGENCONF3, REG_PWMGENCONF5, + REG_PWMGENCONFX_PWMVIBXDUTCYC, + REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX, NORMAL), + SOC_DOUBLE_R("Vibra PWM Duty Cycle P Playback Volume", + REG_PWMGENCONF2, REG_PWMGENCONF4, + REG_PWMGENCONFX_PWMVIBXDUTCYC, + REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX, NORMAL), + + /* TODO: move to DAPM */ + SOC_ENUM("Sidetone Playback Switch", soc_enum_enfirsids), + SOC_ENUM("IHF L and R Bridge Playback Route", soc_enum_parlhf), + SOC_ENUM("Vibra 1 and 2 Bridge Playback Route", soc_enum_parlvib), + + /* Digital gains for AD side */ + + SOC_DOUBLE_R_TLV("LineIn Master Gain Capture Volume", + REG_ADDIGGAIN1, REG_ADDIGGAIN2, + 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Mic Master Gain Capture Volume", + REG_ADDIGGAIN3, REG_ADDIGGAIN4, + 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv), + SOC_DOUBLE_R_TLV("HD Mic Master Gain Capture Volume", + REG_ADDIGGAIN5, REG_ADDIGGAIN6, + 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv), + + /* Digital gains for DA side */ + + SOC_DOUBLE_R_TLV("Headset Master Gain Playback Volume", + REG_DADIGGAIN1, REG_DADIGGAIN2, + 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv), + SOC_DOUBLE_R_TLV("IHF Master Gain Playback Volume", + REG_DADIGGAIN3, REG_DADIGGAIN4, + 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Vibra Master Gain Playback Volume", + REG_DADIGGAIN5, REG_DADIGGAIN6, + 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Analog Loopback Gain Playback Volume", + REG_ADDIGLOOPGAIN1, REG_ADDIGLOOPGAIN2, + 0, REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX, INVERT, dax_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Headset Digital Gain Playback Volume", + REG_HSLEARDIGGAIN, REG_HSRDIGGAIN, + 0, REG_HSLEARDIGGAIN_HSLDGAIN_MAX, INVERT, hs_ear_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Sidetone Digital Gain Playback Volume", + REG_SIDFIRGAIN1, REG_SIDFIRGAIN2, + 0, REG_SIDFIRGAINX_FIRSIDXGAIN_MAX, INVERT, stfir_dig_gain_tlv), + + /* Analog gains */ + + SOC_DOUBLE_TLV("Headset Gain Playback Volume", + REG_ANAGAIN3, + REG_ANAGAIN3_HSLGAIN, REG_ANAGAIN3_HSRGAIN, + REG_ANAGAIN3_HSXGAIN_MAX, INVERT, hs_gain_tlv), + SOC_SINGLE_TLV("Mic 1 Capture Volume", + REG_ANAGAIN1, + REG_ANAGAINX_MICXGAIN, + REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv), + SOC_SINGLE_TLV("Mic 2 Capture Volume", + REG_ANAGAIN2, + REG_ANAGAINX_MICXGAIN, + REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv), + SOC_DOUBLE_TLV("LineIn Capture Volume", + REG_ANAGAIN4, + REG_ANAGAIN4_LINLGAIN, REG_ANAGAIN4_LINRGAIN, + REG_ANAGAIN4_LINXGAIN_MAX, NORMAL, lin_gain_tlv), + SOC_DOUBLE_R_TLV("LineIn to Headset Bypass Playback Volume", + REG_DIGLINHSLGAIN, REG_DIGLINHSRGAIN, + REG_DIGLINHSXGAIN_LINTOHSXGAIN, + REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX, INVERT, lin2hs_gain_tlv), + + /* Digital interface - Clocks */ + SOC_ENUM("Digital Interface Master Generator Switch", soc_enum_mastgen), + SOC_ENUM("Digital Interface 0 Bit-clock Switch", soc_enum_fsbitclk0), + SOC_ENUM("Digital Interface 1 Bit-clock Switch", soc_enum_fsbitclk1), + + /* Digital interface - DA from slot mapping */ + SOC_ENUM("Digital Interface DA 1 From Slot Map", soc_enum_da1slotmap), + SOC_ENUM("Digital Interface DA 2 From Slot Map", soc_enum_da2slotmap), + SOC_ENUM("Digital Interface DA 3 From Slot Map", soc_enum_da3slotmap), + SOC_ENUM("Digital Interface DA 4 From Slot Map", soc_enum_da4slotmap), + SOC_ENUM("Digital Interface DA 5 From Slot Map", soc_enum_da5slotmap), + SOC_ENUM("Digital Interface DA 6 From Slot Map", soc_enum_da6slotmap), + SOC_ENUM("Digital Interface DA 7 From Slot Map", soc_enum_da7slotmap), + SOC_ENUM("Digital Interface DA 8 From Slot Map", soc_enum_da8slotmap), + + /* Digital interface - AD to slot mapping */ + SOC_ENUM("Digital Interface AD To Slot 0 Map", soc_enum_adslot0map), + SOC_ENUM("Digital Interface AD To Slot 1 Map", soc_enum_adslot1map), + SOC_ENUM("Digital Interface AD To Slot 2 Map", soc_enum_adslot2map), + SOC_ENUM("Digital Interface AD To Slot 3 Map", soc_enum_adslot3map), + SOC_ENUM("Digital Interface AD To Slot 4 Map", soc_enum_adslot4map), + SOC_ENUM("Digital Interface AD To Slot 5 Map", soc_enum_adslot5map), + SOC_ENUM("Digital Interface AD To Slot 6 Map", soc_enum_adslot6map), + SOC_ENUM("Digital Interface AD To Slot 7 Map", soc_enum_adslot7map), + SOC_ENUM("Digital Interface AD To Slot 8 Map", soc_enum_adslot8map), + SOC_ENUM("Digital Interface AD To Slot 9 Map", soc_enum_adslot9map), + SOC_ENUM("Digital Interface AD To Slot 10 Map", soc_enum_adslot10map), + SOC_ENUM("Digital Interface AD To Slot 11 Map", soc_enum_adslot11map), + SOC_ENUM("Digital Interface AD To Slot 12 Map", soc_enum_adslot12map), + SOC_ENUM("Digital Interface AD To Slot 13 Map", soc_enum_adslot13map), + SOC_ENUM("Digital Interface AD To Slot 14 Map", soc_enum_adslot14map), + SOC_ENUM("Digital Interface AD To Slot 15 Map", soc_enum_adslot15map), + SOC_ENUM("Digital Interface AD To Slot 16 Map", soc_enum_adslot16map), + SOC_ENUM("Digital Interface AD To Slot 17 Map", soc_enum_adslot17map), + SOC_ENUM("Digital Interface AD To Slot 18 Map", soc_enum_adslot18map), + SOC_ENUM("Digital Interface AD To Slot 19 Map", soc_enum_adslot19map), + SOC_ENUM("Digital Interface AD To Slot 20 Map", soc_enum_adslot20map), + SOC_ENUM("Digital Interface AD To Slot 21 Map", soc_enum_adslot21map), + SOC_ENUM("Digital Interface AD To Slot 22 Map", soc_enum_adslot22map), + SOC_ENUM("Digital Interface AD To Slot 23 Map", soc_enum_adslot23map), + SOC_ENUM("Digital Interface AD To Slot 24 Map", soc_enum_adslot24map), + SOC_ENUM("Digital Interface AD To Slot 25 Map", soc_enum_adslot25map), + SOC_ENUM("Digital Interface AD To Slot 26 Map", soc_enum_adslot26map), + SOC_ENUM("Digital Interface AD To Slot 27 Map", soc_enum_adslot27map), + SOC_ENUM("Digital Interface AD To Slot 28 Map", soc_enum_adslot28map), + SOC_ENUM("Digital Interface AD To Slot 29 Map", soc_enum_adslot29map), + SOC_ENUM("Digital Interface AD To Slot 30 Map", soc_enum_adslot30map), + SOC_ENUM("Digital Interface AD To Slot 31 Map", soc_enum_adslot31map), + + /* Digital interface - Loopback */ + SOC_ENUM("Digital Interface AD 1 Loopback Switch", soc_enum_ad1loop), + SOC_ENUM("Digital Interface AD 2 Loopback Switch", soc_enum_ad2loop), + SOC_ENUM("Digital Interface AD 3 Loopback Switch", soc_enum_ad3loop), + SOC_ENUM("Digital Interface AD 4 Loopback Switch", soc_enum_ad4loop), + SOC_ENUM("Digital Interface AD 5 Loopback Switch", soc_enum_ad5loop), + SOC_ENUM("Digital Interface AD 6 Loopback Switch", soc_enum_ad6loop), + SOC_ENUM("Digital Interface AD 7 Loopback Switch", soc_enum_ad7loop), + SOC_ENUM("Digital Interface AD 8 Loopback Switch", soc_enum_ad8loop), + + /* Digital interface - Burst FIFO */ + SOC_ENUM("Digital Interface 0 FIFO Enable Switch", soc_enum_if0fifoen), + SOC_ENUM("Burst FIFO Mask", soc_enum_bfifomask), + SOC_ENUM("Burst FIFO Bit-clock Frequency", soc_enum_bfifo19m2), + SOC_SINGLE("Burst FIFO Threshold", + REG_FIFOCONF1, + REG_FIFOCONF1_BFIFOINT_SHIFT, + REG_FIFOCONF1_BFIFOINT_MAX, + NORMAL), + SOC_SINGLE("Burst FIFO Length", + REG_FIFOCONF2, + REG_FIFOCONF2_BFIFOTX_SHIFT, + REG_FIFOCONF2_BFIFOTX_MAX, + NORMAL), + SOC_SINGLE("Burst FIFO EOS Extra Slots", + REG_FIFOCONF3, + REG_FIFOCONF3_BFIFOEXSL_SHIFT, + REG_FIFOCONF3_BFIFOEXSL_MAX, + NORMAL), + SOC_SINGLE("Burst FIFO FS Extra Bit-clocks", + REG_FIFOCONF3, + REG_FIFOCONF3_PREBITCLK0_SHIFT, + REG_FIFOCONF3_PREBITCLK0_MAX, + NORMAL), + SOC_ENUM("Burst FIFO Interface Mode", soc_enum_bfifomast), + SOC_ENUM("Burst FIFO Interface Switch", soc_enum_bfifoint), + SOC_SINGLE("Burst FIFO Switch Frame Number", + REG_FIFOCONF4, + REG_FIFOCONF4_BFIFOFRAMSW_SHIFT, + REG_FIFOCONF4_BFIFOFRAMSW_MAX, + NORMAL), + SOC_SINGLE("Burst FIFO Wake Up Delay", + REG_FIFOCONF5, + REG_FIFOCONF5_BFIFOWAKEUP_SHIFT, + REG_FIFOCONF5_BFIFOWAKEUP_MAX, + NORMAL), + SOC_SINGLE("Burst FIFO Samples In FIFO", + REG_FIFOCONF6, + REG_FIFOCONF6_BFIFOSAMPLE_SHIFT, + REG_FIFOCONF6_BFIFOSAMPLE_MAX, + NORMAL), + + /* ANC */ + SOC_SINGLE_S1R("ANC Warp Delay Shift", + REG_ANCCONF2, + REG_ANCCONF2_VALUE_MIN, + REG_ANCCONF2_VALUE_MAX, + 0), + SOC_SINGLE_S1R("ANC FIR Output Shift", + REG_ANCCONF3, + REG_ANCCONF3_VALUE_MIN, + REG_ANCCONF3_VALUE_MAX, + 0), + SOC_SINGLE_S1R("ANC IIR Output Shift", + REG_ANCCONF4, + REG_ANCCONF4_VALUE_MIN, + REG_ANCCONF4_VALUE_MAX, + 0), + SOC_SINGLE_S2R("ANC Warp Delay", + REG_ANCCONF9, REG_ANCCONF10, + REG_ANC_WARP_DELAY_MIN, + REG_ANC_WARP_DELAY_MAX, + 0), + SOC_MULTIPLE_SA("ANC FIR Coefficients", + anc_fir_cache, + REG_ANC_FIR_COEFF_MIN, + REG_ANC_FIR_COEFF_MAX, + 0), + SOC_MULTIPLE_SA("ANC IIR Coefficients", + anc_iir_cache, + REG_ANC_IIR_COEFF_MIN, + REG_ANC_IIR_COEFF_MAX, + 0), + + /* Sidetone */ + SOC_MULTIPLE_SA("Sidetone FIR Coefficients", + sid_fir_cache, + REG_SID_FIR_COEFF_MIN, + REG_SID_FIR_COEFF_MAX, + 0), + SOC_ENUM_EXT("Sidetone FIR Apply Coefficients", + soc_enum_applysidetone, + snd_soc_get_enum_double, + sid_apply_control_put), +}; + +static int ab8500_codec_set_format_if1(struct snd_soc_codec *codec, unsigned int fmt) +{ + unsigned int clear_mask, set_mask; + + /* Master or slave */ + + clear_mask = BMASK(REG_DIGIFCONF3_IF1MASTER); + set_mask = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */ + pr_debug("%s: IF1 Master-mode: AB8500 master.\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF3_IF1MASTER); + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */ + pr_debug("%s: IF1 Master-mode: AB8500 slave.\n", __func__); + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */ + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + pr_err("%s: ERROR: The device is either a master or a slave.\n", + __func__); + default: + pr_err("%s: ERROR: Unsupported master mask 0x%x\n", + __func__, + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + ab8500_codec_update_reg_audio(codec, + REG_DIGIFCONF3, + BMASK(REG_DIGIFCONF3_IF1MASTER), + BMASK(REG_DIGIFCONF3_IF1MASTER)); + + /* I2S or TDM */ + + clear_mask = BMASK(REG_DIGIFCONF4_FSYNC1P) | + BMASK(REG_DIGIFCONF4_BITCLK1P) | + BMASK(REG_DIGIFCONF4_IF1FORMAT1) | + BMASK(REG_DIGIFCONF4_IF1FORMAT0); + set_mask = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: /* I2S mode */ + pr_debug("%s: IF1 Protocol: I2S\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF4_IF1FORMAT1); + break; + case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */ + pr_debug("%s: IF1 Protocol: DSP B (TDM)\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF4_IF1FORMAT0); + break; + default: + pr_err("%s: ERROR: Unsupported format (0x%x)!\n", + __func__, + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask); + + return 0; +} + +static int ab8500_codec_set_word_length_if1(struct snd_soc_codec *codec, unsigned int wl) +{ + unsigned int clear_mask, set_mask; + + clear_mask = BMASK(REG_DIGIFCONF4_IF1WL1) | BMASK(REG_DIGIFCONF4_IF1WL0); + set_mask = 0; + + switch (wl) { + case 16: + break; + case 20: + set_mask |= BMASK(REG_DIGIFCONF4_IF1WL0); + break; + case 24: + set_mask |= BMASK(REG_DIGIFCONF4_IF1WL1); + break; + case 32: + set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1) | + BMASK(REG_DIGIFCONF2_IF0WL0); + break; + default: + pr_err("%s: Unsupporter word-length 0x%x\n", __func__, wl); + return -EINVAL; + } + + pr_debug("%s: Word-length: %d bits.\n", __func__, wl); + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask); + + return 0; +} + +static int ab8500_codec_set_bit_delay_if1(struct snd_soc_codec *codec, unsigned int delay) +{ + unsigned int clear_mask, set_mask; + + clear_mask = BMASK(REG_DIGIFCONF4_IF1DEL); + set_mask = 0; + + switch (delay) { + case 0: + break; + case 1: + set_mask |= BMASK(REG_DIGIFCONF4_IF1DEL); + break; + default: + pr_err("%s: ERROR: Unsupported bit-delay (0x%x)!\n", __func__, delay); + return -EINVAL; + } + + pr_debug("%s: IF1 Bit-delay: %d bits.\n", __func__, delay); + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask); + + return 0; +} + +/* Configures audio macrocell into the AB8500 Chip */ +static void ab8500_codec_configure_audio_macrocell(struct snd_soc_codec *codec) +{ + int data, ret; + + ret = ab8500_sysctrl_write(AB8500_STW4500CTRL3, + AB8500_STW4500CTRL3_CLK32KOUT2DIS | AB8500_STW4500CTRL3_RESETAUDN, + AB8500_STW4500CTRL3_RESETAUDN); + if (ret < 0) + pr_err("%s: WARN: Unable to set reg STW4500CTRL3!\n", __func__); + + data = ab8500_codec_read_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG); + data |= GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | GPIO31_DIR_OUTPUT; + ab8500_codec_write_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG, data); +} + +/* Extended interface for codec-driver */ + +int ab8500_audio_power_control(bool power_on) +{ + int pwr_mask = BMASK(REG_POWERUP_POWERUP) | BMASK(REG_POWERUP_ENANA); + + if (ab8500_codec == NULL) { + pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__); + return -EIO; + } + + pr_debug("%s AB8500.", (power_on) ? "Enabling" : "Disabling"); + + return ab8500_codec_update_reg_audio(ab8500_codec, REG_POWERUP, + pwr_mask, (power_on) ? pwr_mask : REG_MASK_NONE); +} + +void ab8500_audio_pwm_vibra(unsigned char speed_left_pos, + unsigned char speed_left_neg, + unsigned char speed_right_pos, + unsigned char speed_right_neg) +{ + unsigned int clear_mask, set_mask; + bool vibra_on; + + if (ab8500_codec == NULL) { + pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__); + return; + } + + vibra_on = speed_left_pos | speed_left_neg | speed_right_pos | speed_right_neg; + if (!vibra_on) { + speed_left_pos = 0; + speed_left_neg = 0; + speed_right_pos = 0; + speed_right_neg = 0; + } + + pr_debug("%s: PWM-vibra (%d, %d, %d, %d).\n", + __func__, + speed_left_pos, + speed_left_neg, + speed_right_pos, + speed_right_neg); + + set_mask = BMASK(REG_PWMGENCONF1_PWMTOVIB1) | + BMASK(REG_PWMGENCONF1_PWMTOVIB2) | + BMASK(REG_PWMGENCONF1_PWM1CTRL) | + BMASK(REG_PWMGENCONF1_PWM2CTRL) | + BMASK(REG_PWMGENCONF1_PWM1NCTRL) | + BMASK(REG_PWMGENCONF1_PWM1PCTRL) | + BMASK(REG_PWMGENCONF1_PWM2NCTRL) | + BMASK(REG_PWMGENCONF1_PWM2PCTRL); + ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF1, 0x00, set_mask); + + if (speed_left_pos > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX) + speed_left_pos = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX; + ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF3, REG_MASK_ALL, speed_left_pos); + + if (speed_left_neg > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX) + speed_left_neg = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX; + ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF2, REG_MASK_ALL, speed_left_neg); + + if (speed_right_pos > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX) + speed_right_pos = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX; + ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF5, REG_MASK_ALL, speed_right_pos); + + if (speed_right_neg > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX) + speed_right_neg = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX; + ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF4, REG_MASK_ALL, speed_right_neg); + + if (vibra_on) { + clear_mask = 0; + set_mask = BMASK(REG_ANACONF4_ENVIB1) | BMASK(REG_ANACONF4_ENVIB2); + } else { + clear_mask = BMASK(REG_ANACONF4_ENVIB1) | BMASK(REG_ANACONF4_ENVIB2); + set_mask = 0; + }; + ab8500_codec_update_reg_audio(ab8500_codec, REG_ANACONF4, clear_mask, set_mask); +} + +int ab8500_audio_set_word_length(struct snd_soc_dai *dai, unsigned int wl) +{ + unsigned int clear_mask, set_mask; + struct snd_soc_codec *codec = dai->codec; + + clear_mask = BMASK(REG_DIGIFCONF2_IF0WL0) | BMASK(REG_DIGIFCONF2_IF0WL1); + set_mask = 0; + + switch (wl) { + case 16: + break; + case 20: + set_mask |= BMASK(REG_DIGIFCONF2_IF0WL0); + break; + case 24: + set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1); + break; + case 32: + set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1) | + BMASK(REG_DIGIFCONF2_IF0WL0); + break; + default: + pr_err("%s: Unsupported word-length 0x%x\n", __func__, wl); + return -EINVAL; + } + + pr_debug("%s: IF0 Word-length: %d bits.\n", __func__, wl); + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask); + + return 0; +} + +int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay) +{ + unsigned int clear_mask, set_mask; + struct snd_soc_codec *codec = dai->codec; + + clear_mask = BMASK(REG_DIGIFCONF2_IF0DEL); + set_mask = 0; + + switch (delay) { + case 0: + break; + case 1: + set_mask |= BMASK(REG_DIGIFCONF2_IF0DEL); + break; + default: + pr_err("%s: ERROR: Unsupported bit-delay (0x%x)!\n", __func__, delay); + return -EINVAL; + } + + pr_debug("%s: IF0 Bit-delay: %d bits.\n", __func__, delay); + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask); + + return 0; +} + +int ab8500_audio_setup_if1(struct snd_soc_codec *codec, + unsigned int fmt, + unsigned int wl, + unsigned int delay) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + ret = ab8500_codec_set_format_if1(codec, fmt); + if (ret) + return -1; + + ret = ab8500_codec_set_bit_delay_if1(codec, delay); + if (ret) + return -1; + + + ret = ab8500_codec_set_word_length_if1(codec, wl); + if (ret) + return -1; + + return 0; +} + +/* ANC FIR-coefficients configuration sequence */ +static void ab8500_audio_anc_fir(struct snd_soc_codec *codec, + unsigned int bank, unsigned int param) +{ + if (param == 0 && bank == 0) + snd_soc_update_bits(codec, REG_ANCCONF1, + REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCFIRUPDATE)); + + snd_soc_write(codec, REG_ANCCONF5, + anc_fir_cache[param] >> 8 & REG_MASK_ALL); + snd_soc_write(codec, REG_ANCCONF6, + anc_fir_cache[param] & REG_MASK_ALL); + + if (param == REG_ANC_FIR_COEFFS - 1 && bank == 1) + snd_soc_update_bits(codec, REG_ANCCONF1, + BMASK(REG_ANCCONF1_ANCFIRUPDATE), REG_MASK_NONE); +} + +/* ANC IIR-coefficients configuration sequence */ +static void ab8500_audio_anc_iir(struct snd_soc_codec *codec, + unsigned int bank, unsigned int param) +{ + if (param == 0) { + if (bank == 0) { + snd_soc_update_bits(codec, REG_ANCCONF1, + REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCIIRINIT)); + usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY); + snd_soc_update_bits(codec, REG_ANCCONF1, + BMASK(REG_ANCCONF1_ANCIIRINIT), REG_MASK_NONE); + usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY); + } else { + snd_soc_update_bits(codec, REG_ANCCONF1, + REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCIIRUPDATE)); + } + } else if (param > 3) { + snd_soc_write(codec, REG_ANCCONF7, REG_MASK_NONE); + snd_soc_write(codec, REG_ANCCONF8, + anc_iir_cache[param] >> 16 & REG_MASK_ALL); + } + + snd_soc_write(codec, REG_ANCCONF7, + anc_iir_cache[param] >> 8 & REG_MASK_ALL); + snd_soc_write(codec, REG_ANCCONF8, + anc_iir_cache[param] & REG_MASK_ALL); + + if (param == REG_ANC_IIR_COEFFS - 1 && bank == 1) + snd_soc_update_bits(codec, REG_ANCCONF1, + BMASK(REG_ANCCONF1_ANCIIRUPDATE), REG_MASK_NONE); +} + +/* ANC IIR-/FIR-coefficients configuration sequence */ +void ab8500_audio_anc_configure(struct snd_soc_codec *codec, + bool apply_fir, bool apply_iir) +{ + unsigned int bank, param; + + pr_debug("%s: Enter.\n", __func__); + + if (apply_fir) + snd_soc_update_bits(codec, REG_ANCCONF1, + BMASK(REG_ANCCONF1_ENANC), REG_MASK_NONE); + + snd_soc_update_bits(codec, REG_ANCCONF1, + REG_MASK_NONE, BMASK(REG_ANCCONF1_ENANC)); + + if (apply_fir) + for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) + for (param = 0; param < REG_ANC_FIR_COEFFS; param++) + ab8500_audio_anc_fir(codec, bank, param); + + if (apply_iir) + for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) + for (param = 0; param < REG_ANC_IIR_COEFFS; param++) + ab8500_audio_anc_iir(codec, bank, param); + + pr_debug("%s: Exit.\n", __func__); +} + +int ab8500_audio_set_adcm(enum ab8500_audio_adcm req_adcm) +{ + if (ab8500_codec == NULL) { + pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", + __func__); + return -EIO; + } + + if (adcm == req_adcm) + return 0; + + pr_debug("%s: Enter.\n", __func__); + + if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm || + AB8500_AUDIO_ADCM_FORCE_DOWN == req_adcm) { + + mutex_lock(&ab8500_codec->mutex); + + adcm_anaconf5 = snd_soc_read(ab8500_codec, REG_ANACONF5); + adcm_muteconf = snd_soc_read(ab8500_codec, REG_MUTECONF); + adcm_anaconf4 = snd_soc_read(ab8500_codec, REG_ANACONF4); + + if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm) { + snd_soc_update_bits(ab8500_codec, REG_ANACONF5, + REG_MASK_NONE, ADCM_ANACONF5_MASK); + snd_soc_update_bits(ab8500_codec, REG_MUTECONF, + REG_MASK_NONE, ADCM_MUTECONF_MASK); + snd_soc_update_bits(ab8500_codec, REG_ANACONF4, + REG_MASK_NONE, ADCM_ANACONF4_MASK); + } else { + snd_soc_update_bits(ab8500_codec, REG_ANACONF5, + ADCM_ANACONF5_MASK, REG_MASK_NONE); + } + } else if (AB8500_AUDIO_ADCM_NORMAL == req_adcm) { + if (AB8500_AUDIO_ADCM_FORCE_UP == adcm) { + snd_soc_update_bits(ab8500_codec, REG_ANACONF5, + ~adcm_anaconf5 & ADCM_ANACONF5_MASK, + adcm_anaconf5 & ADCM_ANACONF5_MASK); + snd_soc_update_bits(ab8500_codec, REG_MUTECONF, + ~adcm_muteconf & ADCM_MUTECONF_MASK, + adcm_muteconf & ADCM_MUTECONF_MASK); + snd_soc_update_bits(ab8500_codec, REG_ANACONF4, + ~adcm_anaconf4 & ADCM_ANACONF4_MASK, + adcm_anaconf4 & ADCM_ANACONF4_MASK); + } else { + snd_soc_update_bits(ab8500_codec, REG_ANACONF5, + ~adcm_anaconf5 & ADCM_ANACONF5_MASK, + adcm_anaconf5 & ADCM_ANACONF5_MASK); + } + } + + adcm = req_adcm; + + if (AB8500_AUDIO_ADCM_NORMAL == adcm) + mutex_unlock(&ab8500_codec->mutex); + + pr_debug("%s: Exit.\n", __func__); + + return 0; +} + +static int ab8500_codec_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(&codec->dapm, ab8500_dapm_widgets, + ARRAY_SIZE(ab8500_dapm_widgets)); + if (ret < 0) { + pr_err("%s: Failed to create DAPM controls (%d).\n", + __func__, ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&codec->dapm, dapm_routes, ARRAY_SIZE(dapm_routes)); + if (ret < 0) { + pr_err("%s: Failed to add DAPM routes (%d).\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int ab8500_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static int ab8500_codec_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static int ab8500_codec_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static void ab8500_codec_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("%s Enter.\n", __func__); +} + +static int ab8500_codec_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + pr_err("%s Enter.\n", __func__); + + return 0; +} + +/* Gates clocking according format mask */ +static int ab8500_codec_set_dai_clock_gate(struct snd_soc_codec *codec, unsigned int fmt) +{ + unsigned int clear_mask; + unsigned int set_mask; + + clear_mask = BMASK(REG_DIGIFCONF1_ENMASTGEN) | + BMASK(REG_DIGIFCONF1_ENFSBITCLK0); + + set_mask = BMASK(REG_DIGIFCONF1_ENMASTGEN); + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: /* continuous clock */ + pr_debug("%s: IF0 Clock is continuous.\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF1_ENFSBITCLK0); + break; + case SND_SOC_DAIFMT_GATED: /* clock is gated */ + pr_debug("%s: IF0 Clock is gated.\n", __func__); + break; + default: + pr_err("%s: ERROR: Unsupported clock mask (0x%x)!\n", + __func__, + fmt & SND_SOC_DAIFMT_CLOCK_MASK); + return -EINVAL; + } + + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF1, clear_mask, set_mask); + + return 0; +} + +static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + unsigned int clear_mask; + unsigned int set_mask; + struct snd_soc_codec *codec = dai->codec; + int err; + + pr_debug("%s: Enter (fmt = 0x%x)\n", __func__, fmt); + + clear_mask = BMASK(REG_DIGIFCONF3_IF1DATOIF0AD) | + BMASK(REG_DIGIFCONF3_IF1CLKTOIF0CLK) | + BMASK(REG_DIGIFCONF3_IF0BFIFOEN) | + BMASK(REG_DIGIFCONF3_IF0MASTER); + set_mask = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */ + pr_debug("%s: IF0 Master-mode: AB8500 master.\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF3_IF0MASTER); + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */ + pr_debug("%s: IF0 Master-mode: AB8500 slave.\n", __func__); + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */ + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + pr_err("%s: ERROR: The device is either a master or a slave.\n", __func__); + default: + pr_err("%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, + (fmt & SND_SOC_DAIFMT_MASTER_MASK)); + return -EINVAL; + break; + } + + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF3, clear_mask, set_mask); + + /* Set clock gating */ + err = ab8500_codec_set_dai_clock_gate(codec, fmt); + if (err) { + pr_err("%s: ERRROR: Failed to set clock gate (%d).\n", __func__, err); + return err; + } + + /* Setting data transfer format */ + + clear_mask = BMASK(REG_DIGIFCONF2_IF0FORMAT0) | + BMASK(REG_DIGIFCONF2_IF0FORMAT1) | + BMASK(REG_DIGIFCONF2_FSYNC0P) | + BMASK(REG_DIGIFCONF2_BITCLK0P); + set_mask = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: /* I2S mode */ + pr_debug("%s: IF0 Protocol: I2S\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT1); + + /* 32 bit, 0 delay */ + ab8500_audio_set_word_length(dai, 32); + ab8500_audio_set_bit_delay(dai, 0); + + break; + case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */ + pr_debug("%s: IF0 Protocol: DSP A (TDM)\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0); + break; + case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */ + pr_debug("%s: IF0 Protocol: DSP B (TDM)\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0); + break; + default: + pr_err("%s: ERROR: Unsupported format (0x%x)!\n", + __func__, + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + pr_debug("%s: IF0: Normal bit clock, normal frame\n", __func__); + break; + case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */ + pr_debug("%s: IF0: Normal bit clock, inverted frame\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P); + break; + case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */ + pr_debug("%s: IF0: Inverted bit clock, normal frame\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P); + break; + case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */ + pr_debug("%s: IF0: Inverted bit clock, inverted frame\n", __func__); + set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P); + set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P); + break; + default: + pr_err("%s: ERROR: Unsupported INV mask 0x%x\n", + __func__, + (fmt & SND_SOC_DAIFMT_INV_MASK)); + return -EINVAL; + break; + } + + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask); + + return 0; +} + +static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int set_mask, clear_mask, slots_active; + + /* Only 16 bit slot width is supported at the moment in TDM mode */ + if (slot_width != 16) { + pr_err("%s: ERROR: Unsupported slot_width %d.\n", + __func__, slot_width); + return -EINVAL; + } + + /* Setup TDM clocking according to slot count */ + pr_debug("%s: Slots, total: %d\n", __func__, slots); + clear_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) | + BMASK(REG_DIGIFCONF1_IF0BITCLKOS1); + switch (slots) { + case 2: + set_mask = REG_MASK_NONE; + break; + case 4: + set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0); + break; + case 8: + set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS1); + break; + case 16: + set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) | + BMASK(REG_DIGIFCONF1_IF0BITCLKOS1); + break; + default: + pr_err("%s: ERROR: Unsupported number of slots (%d)!\n", __func__, slots); + return -EINVAL; + } + ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF1, clear_mask, set_mask); + + /* Setup TDM DA according to active tx slots */ + clear_mask = REG_DASLOTCONFX_SLTODAX_MASK; + slots_active = hweight32(tx_mask); + pr_debug("%s: Slots, active, TX: %d\n", __func__, slots_active); + switch (slots_active) { + case 0: + break; + case 1: + /* Slot 9 -> DA_IN1 & DA_IN3 */ + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF1, clear_mask, 9); + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF3, clear_mask, 9); + break; + case 2: + /* Slot 9 -> DA_IN1 & DA_IN3, Slot 11 -> DA_IN2 & DA_IN4 */ + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF1, clear_mask, 9); + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF3, clear_mask, 9); + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF2, clear_mask, 11); + ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF4, clear_mask, 11); + + break; + case 8: + pr_debug("%s: In 8-channel mode DA-from-slot mapping is set manually.", __func__); + break; + default: + pr_err("%s: Unsupported number of active TX-slots (%d)!\n", __func__, slots_active); + return -EINVAL; + } + + /* Setup TDM AD according to active RX-slots */ + slots_active = hweight32(rx_mask); + pr_debug("%s: Slots, active, RX: %d\n", __func__, slots_active); + switch (slots_active) { + case 0: + break; + case 1: + /* AD_OUT3 -> slot 0 & 1 */ + ab8500_codec_update_reg_audio(codec, REG_ADSLOTSEL1, + REG_MASK_ALL, + REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN | + REG_ADSLOTSELX_AD_OUT3_TO_SLOT_ODD); + break; + case 2: + /* AD_OUT3 -> slot 0, AD_OUT2 -> slot 1 */ + ab8500_codec_update_reg_audio(codec, REG_ADSLOTSEL1, + REG_MASK_ALL, + REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN | + REG_ADSLOTSELX_AD_OUT2_TO_SLOT_ODD); + break; + case 8: + pr_debug("%s: In 8-channel mode AD-to-slot mapping is set manually.", __func__); + break; + default: + pr_err("%s: Unsupported number of active RX-slots (%d)!\n", __func__, slots_active); + return -EINVAL; + } + + return 0; +} + +struct snd_soc_dai_driver ab8500_codec_dai[] = { + { + .name = "ab8500-codec-dai.0", + .id = 0, + .playback = { + .stream_name = "ab8500_0p", + .channels_min = 1, + .channels_max = 8, + .rates = AB8500_SUPPORTED_RATE, + .formats = AB8500_SUPPORTED_FMT, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .startup = ab8500_codec_pcm_startup, + .prepare = ab8500_codec_pcm_prepare, + .hw_params = ab8500_codec_pcm_hw_params, + .shutdown = ab8500_codec_pcm_shutdown, + .set_sysclk = ab8500_codec_set_dai_sysclk, + .set_tdm_slot = ab8500_codec_set_dai_tdm_slot, + .set_fmt = ab8500_codec_set_dai_fmt, + } + }, + .symmetric_rates = 1 + }, + { + .name = "ab8500-codec-dai.1", + .id = 1, + .capture = { + .stream_name = "ab8500_0c", + .channels_min = 1, + .channels_max = 8, + .rates = AB8500_SUPPORTED_RATE, + .formats = AB8500_SUPPORTED_FMT, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .startup = ab8500_codec_pcm_startup, + .prepare = ab8500_codec_pcm_prepare, + .hw_params = ab8500_codec_pcm_hw_params, + .shutdown = ab8500_codec_pcm_shutdown, + .set_sysclk = ab8500_codec_set_dai_sysclk, + .set_tdm_slot = ab8500_codec_set_dai_tdm_slot, + .set_fmt = ab8500_codec_set_dai_fmt, + } + }, + .symmetric_rates = 1 + } +}; + +static int ab8500_codec_probe(struct snd_soc_codec *codec) +{ + int i, ret; + u8 *cache = codec->reg_cache; + + pr_debug("%s: Enter.\n", __func__); + + ab8500_codec_configure_audio_macrocell(codec); + + for (i = REG_AUDREV; i >= REG_POWERUP; i--) + ab8500_codec_write_reg_audio(codec, i, cache[i]); + + /* Add controls */ + ret = snd_soc_add_controls(codec, ab8500_snd_controls, + ARRAY_SIZE(ab8500_snd_controls)); + if (ret < 0) { + pr_err("%s: failed to add soc controls (%d).\n", + __func__, ret); + return ret; + } + + /* Add DAPM-widgets */ + ret = ab8500_codec_add_widgets(codec); + if (ret < 0) { + pr_err("%s: Failed add widgets (%d).\n", __func__, ret); + return ret; + } + + ab8500_codec = codec; + + return ret; +} + +static int ab8500_codec_remove(struct snd_soc_codec *codec) +{ + snd_soc_dapm_free(&codec->dapm); + ab8500_codec = NULL; + + return 0; +} + +static int ab8500_codec_suspend(struct snd_soc_codec *codec, + pm_message_t state) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static int ab8500_codec_resume(struct snd_soc_codec *codec) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +struct snd_soc_codec_driver ab8500_codec_driver = { + .probe = ab8500_codec_probe, + .remove = ab8500_codec_remove, + .suspend = ab8500_codec_suspend, + .resume = ab8500_codec_resume, + .read = ab8500_codec_read_reg_audio, + .write = ab8500_codec_write_reg_audio, + .reg_cache_size = ARRAY_SIZE(ab8500_reg_cache), + .reg_word_size = sizeof(u8), + .reg_cache_default = ab8500_reg_cache, +}; + +static int __devinit ab8500_codec_driver_probe(struct platform_device *pdev) +{ + int err; + + pr_debug("%s: Enter.\n", __func__); + + pr_info("%s: Register codec.\n", __func__); + err = snd_soc_register_codec(&pdev->dev, + &ab8500_codec_driver, + ab8500_codec_dai, + ARRAY_SIZE(ab8500_codec_dai)); + + if (err < 0) { + pr_err("%s: Error: Failed to register codec (%d).\n", + __func__, err); + } + + return err; +} + +static int __devexit ab8500_codec_driver_remove(struct platform_device *pdev) +{ + pr_info("%s Enter.\n", __func__); + + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static int ab8500_codec_driver_suspend(struct platform_device *pdev, + pm_message_t state) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static int ab8500_codec_driver_resume(struct platform_device *pdev) +{ + pr_debug("%s Enter.\n", __func__); + + return 0; +} + +static struct platform_driver ab8500_codec_platform_driver = { + .driver = { + .name = "ab8500-codec", + .owner = THIS_MODULE, + }, + .probe = ab8500_codec_driver_probe, + .remove = __devexit_p(ab8500_codec_driver_remove), + .suspend = ab8500_codec_driver_suspend, + .resume = ab8500_codec_driver_resume, +}; + +static int __devinit ab8500_codec_platform_driver_init(void) +{ + int ret; + + pr_info("%s: Enter.\n", __func__); + + ret = platform_driver_register(&ab8500_codec_platform_driver); + if (ret != 0) { + pr_err("%s: Failed to register AB8500 platform driver (%d)!\n", + __func__, ret); + } + + return ret; +} + +static void __exit ab8500_codec_platform_driver_exit(void) +{ + pr_info("%s: Enter.\n", __func__); + + platform_driver_unregister(&ab8500_codec_platform_driver); +} + +module_init(ab8500_codec_platform_driver_init); +module_exit(ab8500_codec_platform_driver_exit); + +MODULE_DESCRIPTION("AB8500 Codec driver"); +MODULE_ALIAS("platform:ab8500-codec"); +MODULE_AUTHOR("ST-Ericsson"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ab8500_audio.h b/sound/soc/codecs/ab8500_audio.h new file mode 100644 index 00000000000..0b3d9bc92fa --- /dev/null +++ b/sound/soc/codecs/ab8500_audio.h @@ -0,0 +1,683 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Mikko J. Lehto <mikko.lehto@symbio.com>, + * Mikko Sarmanne <mikko.sarmanne@symbio.com>, + * Ola Lilja <ola.o.lilja@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. + */ + +#ifndef AB8500_CODEC_REGISTERS_H +#define AB8500_CODEC_REGISTERS_H + +#define AB8500_SUPPORTED_RATE (SNDRV_PCM_RATE_48000) +#define AB8500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE) + +extern struct snd_soc_dai_driver ab8500_codec_dai[]; +extern struct snd_soc_codec_driver soc_codec_dev_ab8500; + +#define LINEIN_RAMP_DELAY 50 + +/* Extended interface for codec-driver */ + +int ab8500_audio_power_control(bool power_on); +void ab8500_audio_pwm_vibra(unsigned char speed_left_pos, + unsigned char speed_left_neg, + unsigned char speed_right_pos, + unsigned char speed_right_neg); +int ab8500_audio_set_word_length(struct snd_soc_dai *dai, unsigned int wl); +int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay); +int ab8500_audio_setup_if1(struct snd_soc_codec *codec, + unsigned int fmt, + unsigned int wl, + unsigned int delay); + +void ab8500_audio_anc_configure(struct snd_soc_codec *codec, + bool apply_fir, bool apply_iir); + +enum ab8500_audio_adcm { + AB8500_AUDIO_ADCM_NORMAL, + AB8500_AUDIO_ADCM_FORCE_UP, + AB8500_AUDIO_ADCM_FORCE_DOWN +}; +int ab8500_audio_set_adcm(enum ab8500_audio_adcm req_adcm); + +#define SOC_SINGLE_VALUE_S1R(xreg0, xcount, xmin, xmax, xinvert) \ + ((unsigned long)&(struct soc_smra_control) \ + { .reg = ((unsigned int[]){ xreg0 }), \ + .rcount = 1, .count = xcount, \ + .invert = xinvert, .min = xmin, .max = xmax}) + +#define SOC_SINGLE_VALUE_S2R(xreg0, xreg1, xcount, xmin, xmax, xinvert) \ + ((unsigned long)&(struct soc_smra_control) \ + {.reg = ((unsigned int[]){ xreg0, xreg1 }), \ + .rcount = 2, .count = xcount, \ + .min = xmin, .max = xmax, .invert = xinvert}) + +#define SOC_SINGLE_VALUE_S4R(xreg0, xreg1, xreg2, xreg3, \ + xcount, xmin, xmax, xinvert) \ + ((unsigned long)&(struct soc_smra_control) \ + {.reg = ((unsigned int[]){ xreg0, xreg1, xreg2, xreg3 }), \ + .rcount = 4, .count = xcount, \ + .min = xmin, .max = xmax, .invert = xinvert}) + +#define SOC_SINGLE_VALUE_S8R(xreg0, xreg1, xreg2, xreg3, \ + xreg4, xreg5, xreg6, xreg7, xcount, xmin, xmax, xinvert) \ + ((unsigned long)&(struct soc_smra_control) \ + {.reg = ((unsigned int[]){ xreg0, xreg1, xreg2, xreg3, \ + xreg4, xreg5, xreg6, xreg7 }), \ + .rcount = 8, .count = xcount, \ + .min = xmin, .max = xmax, .invert = xinvert}) + +#define SOC_MULTIPLE_VALUE_SA(xvalues, xcount, xmin, xmax, xinvert) \ + ((unsigned long)&(struct soc_smra_control) \ + {.values = xvalues, .rcount = 1, .count = xcount, \ + .min = xmin, .max = xmax, .invert = xinvert}) + +#define SOC_ENUM_STROBE_DECL(name, xreg, xbit, xinvert, xtexts) \ + struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xbit, \ + xinvert, 2, xtexts) + +/* Extended SOC macros */ + +#define SOC_SINGLE_S1R(xname, reg0, min, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \ + .private_value = SOC_SINGLE_VALUE_S1R(reg0, 1, min, max, invert) } + +#define SOC_SINGLE_S2R(xname, reg0, reg1, min, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \ + .private_value = SOC_SINGLE_VALUE_S2R(reg0, reg1, 1, min, max, invert) } + +#define SOC_SINGLE_S4R(xname, reg0, reg1, reg2, reg3, min, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \ + .private_value = SOC_SINGLE_VALUE_S4R(reg0, reg1, reg2, reg3, \ + 1, min, max, invert) } + +#define SOC_SINGLE_S8R(xname, reg0, reg1, reg2, reg3, \ + reg4, reg5, reg6, reg7, min, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \ + .private_value = SOC_SINGLE_VALUE_S4R(reg0, reg1, reg2, reg3, \ + reg4, reg5, reg6, reg7\ + 1, min, max, invert) } + +#define SOC_MULTIPLE_SA(xname, values, min, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_s, .get = snd_soc_get_sa, .put = snd_soc_put_sa, \ + .private_value = SOC_MULTIPLE_VALUE_SA(values, ARRAY_SIZE(values), \ + min, max, invert) } + +#define SOC_ENUM_STROBE(xname, xenum) \ + SOC_ENUM_EXT(xname, xenum, \ + snd_soc_get_enum_strobe, \ + snd_soc_put_enum_strobe) + +/* AB8500 audio bank (0x0d) register definitions */ + +#define REG_POWERUP 0x00 +#define REG_AUDSWRESET 0x01 +#define REG_ADPATHENA 0x02 +#define REG_DAPATHENA 0x03 +#define REG_ANACONF1 0x04 +#define REG_ANACONF2 0x05 +#define REG_DIGMICCONF 0x06 +#define REG_ANACONF3 0x07 +#define REG_ANACONF4 0x08 +#define REG_DAPATHCONF 0x09 +#define REG_MUTECONF 0x0A +#define REG_SHORTCIRCONF 0x0B +#define REG_ANACONF5 0x0C +#define REG_ENVCPCONF 0x0D +#define REG_SIGENVCONF 0x0E +#define REG_PWMGENCONF1 0x0F +#define REG_PWMGENCONF2 0x10 +#define REG_PWMGENCONF3 0x11 +#define REG_PWMGENCONF4 0x12 +#define REG_PWMGENCONF5 0x13 +#define REG_ANAGAIN1 0x14 +#define REG_ANAGAIN2 0x15 +#define REG_ANAGAIN3 0x16 +#define REG_ANAGAIN4 0x17 +#define REG_DIGLINHSLGAIN 0x18 +#define REG_DIGLINHSRGAIN 0x19 +#define REG_ADFILTCONF 0x1A +#define REG_DIGIFCONF1 0x1B +#define REG_DIGIFCONF2 0x1C +#define REG_DIGIFCONF3 0x1D +#define REG_DIGIFCONF4 0x1E +#define REG_ADSLOTSEL1 0x1F +#define REG_ADSLOTSEL2 0x20 +#define REG_ADSLOTSEL3 0x21 +#define REG_ADSLOTSEL4 0x22 +#define REG_ADSLOTSEL5 0x23 +#define REG_ADSLOTSEL6 0x24 +#define REG_ADSLOTSEL7 0x25 +#define REG_ADSLOTSEL8 0x26 +#define REG_ADSLOTSEL9 0x27 +#define REG_ADSLOTSEL10 0x28 +#define REG_ADSLOTSEL11 0x29 +#define REG_ADSLOTSEL12 0x2A +#define REG_ADSLOTSEL13 0x2B +#define REG_ADSLOTSEL14 0x2C +#define REG_ADSLOTSEL15 0x2D +#define REG_ADSLOTSEL16 0x2E +#define REG_ADSLOTHIZCTRL1 0x2F +#define REG_ADSLOTHIZCTRL2 0x30 +#define REG_ADSLOTHIZCTRL3 0x31 +#define REG_ADSLOTHIZCTRL4 0x32 +#define REG_DASLOTCONF1 0x33 +#define REG_DASLOTCONF2 0x34 +#define REG_DASLOTCONF3 0x35 +#define REG_DASLOTCONF4 0x36 +#define REG_DASLOTCONF5 0x37 +#define REG_DASLOTCONF6 0x38 +#define REG_DASLOTCONF7 0x39 +#define REG_DASLOTCONF8 0x3A +#define REG_CLASSDCONF1 0x3B +#define REG_CLASSDCONF2 0x3C +#define REG_CLASSDCONF3 0x3D +#define REG_DMICFILTCONF 0x3E +#define REG_DIGMULTCONF1 0x3F +#define REG_DIGMULTCONF2 0x40 +#define REG_ADDIGGAIN1 0x41 +#define REG_ADDIGGAIN2 0x42 +#define REG_ADDIGGAIN3 0x43 +#define REG_ADDIGGAIN4 0x44 +#define REG_ADDIGGAIN5 0x45 +#define REG_ADDIGGAIN6 0x46 +#define REG_DADIGGAIN1 0x47 +#define REG_DADIGGAIN2 0x48 +#define REG_DADIGGAIN3 0x49 +#define REG_DADIGGAIN4 0x4A +#define REG_DADIGGAIN5 0x4B +#define REG_DADIGGAIN6 0x4C +#define REG_ADDIGLOOPGAIN1 0x4D +#define REG_ADDIGLOOPGAIN2 0x4E +#define REG_HSLEARDIGGAIN 0x4F +#define REG_HSRDIGGAIN 0x50 +#define REG_SIDFIRGAIN1 0x51 +#define REG_SIDFIRGAIN2 0x52 +#define REG_ANCCONF1 0x53 +#define REG_ANCCONF2 0x54 +#define REG_ANCCONF3 0x55 +#define REG_ANCCONF4 0x56 +#define REG_ANCCONF5 0x57 +#define REG_ANCCONF6 0x58 +#define REG_ANCCONF7 0x59 +#define REG_ANCCONF8 0x5A +#define REG_ANCCONF9 0x5B +#define REG_ANCCONF10 0x5C +#define REG_ANCCONF11 0x5D +#define REG_ANCCONF12 0x5E +#define REG_ANCCONF13 0x5F +#define REG_ANCCONF14 0x60 +#define REG_SIDFIRADR 0x61 +#define REG_SIDFIRCOEF1 0x62 +#define REG_SIDFIRCOEF2 0x63 +#define REG_SIDFIRCONF 0x64 +#define REG_AUDINTMASK1 0x65 +#define REG_AUDINTSOURCE1 0x66 +#define REG_AUDINTMASK2 0x67 +#define REG_AUDINTSOURCE2 0x68 +#define REG_FIFOCONF1 0x69 +#define REG_FIFOCONF2 0x6A +#define REG_FIFOCONF3 0x6B +#define REG_FIFOCONF4 0x6C +#define REG_FIFOCONF5 0x6D +#define REG_FIFOCONF6 0x6E +#define REG_AUDREV 0x6F + +#define AB8500_FIRST_REG REG_POWERUP +#define AB8500_LAST_REG REG_AUDREV +#define AB8500_CACHEREGNUM (AB8500_LAST_REG + 1) + + +#define REG_MASK_ALL 0xFF +#define REG_MASK_NONE 0x00 + +/* REG_POWERUP */ +#define REG_POWERUP_POWERUP 7 +#define REG_POWERUP_ENANA 3 + +/* REG_AUDSWRESET */ +#define REG_AUDSWRESET_SWRESET 7 + +/* REG_ADPATHENA */ +#define REG_ADPATHENA_ENAD12 7 +#define REG_ADPATHENA_ENAD34 5 +#define REG_ADPATHENA_ENAD5768 3 + +/* REG_DAPATHENA */ +#define REG_DAPATHENA_ENDA1 7 +#define REG_DAPATHENA_ENDA2 6 +#define REG_DAPATHENA_ENDA3 5 +#define REG_DAPATHENA_ENDA4 4 +#define REG_DAPATHENA_ENDA5 3 +#define REG_DAPATHENA_ENDA6 2 + +/* REG_ANACONF1 */ +#define REG_ANACONF1_HSLOWPOW 7 +#define REG_ANACONF1_DACLOWPOW1 6 +#define REG_ANACONF1_DACLOWPOW0 5 +#define REG_ANACONF1_EARDACLOWPOW 4 +#define REG_ANACONF1_EARSELCM 2 +#define REG_ANACONF1_HSHPEN 1 +#define REG_ANACONF1_EARDRVLOWPOW 0 + +/* REG_ANACONF2 */ +#define REG_ANACONF2_ENMIC1 7 +#define REG_ANACONF2_ENMIC2 6 +#define REG_ANACONF2_ENLINL 5 +#define REG_ANACONF2_ENLINR 4 +#define REG_ANACONF2_MUTMIC1 3 +#define REG_ANACONF2_MUTMIC2 2 +#define REG_ANACONF2_MUTLINL 1 +#define REG_ANACONF2_MUTLINR 0 + +/* REG_DIGMICCONF */ +#define REG_DIGMICCONF_ENDMIC1 7 +#define REG_DIGMICCONF_ENDMIC2 6 +#define REG_DIGMICCONF_ENDMIC3 5 +#define REG_DIGMICCONF_ENDMIC4 4 +#define REG_DIGMICCONF_ENDMIC5 3 +#define REG_DIGMICCONF_ENDMIC6 2 +#define REG_DIGMICCONF_HSFADSPEED 0 + +/* REG_ANACONF3 */ +#define REG_ANACONF3_MIC1SEL 7 +#define REG_ANACONF3_LINRSEL 6 +#define REG_ANACONF3_ENDRVHSL 5 +#define REG_ANACONF3_ENDRVHSR 4 +#define REG_ANACONF3_ENADCMIC 2 +#define REG_ANACONF3_ENADCLINL 1 +#define REG_ANACONF3_ENADCLINR 0 + +/* REG_ANACONF4 */ +#define REG_ANACONF4_DISPDVSS 7 +#define REG_ANACONF4_ENEAR 6 +#define REG_ANACONF4_ENHSL 5 +#define REG_ANACONF4_ENHSR 4 +#define REG_ANACONF4_ENHFL 3 +#define REG_ANACONF4_ENHFR 2 +#define REG_ANACONF4_ENVIB1 1 +#define REG_ANACONF4_ENVIB2 0 + +/* REG_DAPATHCONF */ +#define REG_DAPATHCONF_ENDACEAR 6 +#define REG_DAPATHCONF_ENDACHSL 5 +#define REG_DAPATHCONF_ENDACHSR 4 +#define REG_DAPATHCONF_ENDACHFL 3 +#define REG_DAPATHCONF_ENDACHFR 2 +#define REG_DAPATHCONF_ENDACVIB1 1 +#define REG_DAPATHCONF_ENDACVIB2 0 + +/* REG_MUTECONF */ +#define REG_MUTECONF_MUTEAR 6 +#define REG_MUTECONF_MUTHSL 5 +#define REG_MUTECONF_MUTHSR 4 +#define REG_MUTECONF_MUTDACEAR 2 +#define REG_MUTECONF_MUTDACHSL 1 +#define REG_MUTECONF_MUTDACHSR 0 + +/* REG_SHORTCIRCONF */ +#define REG_SHORTCIRCONF_ENSHORTPWD 7 +#define REG_SHORTCIRCONF_EARSHORTDIS 6 +#define REG_SHORTCIRCONF_HSSHORTDIS 5 +#define REG_SHORTCIRCONF_HSPULLDEN 4 +#define REG_SHORTCIRCONF_HSOSCEN 2 +#define REG_SHORTCIRCONF_HSFADDIS 1 +#define REG_SHORTCIRCONF_HSZCDDIS 0 /* Zero cross should be disabled */ + +/* REG_ANACONF5 */ +#define REG_ANACONF5_ENCPHS 7 +#define REG_ANACONF5_HSLDACTOLOL 5 +#define REG_ANACONF5_HSRDACTOLOR 4 +#define REG_ANACONF5_ENLOL 3 +#define REG_ANACONF5_ENLOR 2 +#define REG_ANACONF5_HSAUTOEN 0 + +/* REG_ENVCPCONF */ +#define REG_ENVCPCONF_ENVDETHTHRE 4 +#define REG_ENVCPCONF_ENVDETLTHRE 0 +#define REG_ENVCPCONF_ENVDETHTHRE_MAX 0x0F +#define REG_ENVCPCONF_ENVDETLTHRE_MAX 0x0F + +/* REG_SIGENVCONF */ +#define REG_SIGENVCONF_CPLVEN 5 +#define REG_SIGENVCONF_ENVDETCPEN 4 +#define REG_SIGENVCONF_ENVDETTIME 0 +#define REG_SIGENVCONF_ENVDETTIME_MAX 0x0F + +/* REG_PWMGENCONF1 */ +#define REG_PWMGENCONF1_PWMTOVIB1 7 +#define REG_PWMGENCONF1_PWMTOVIB2 6 +#define REG_PWMGENCONF1_PWM1CTRL 5 +#define REG_PWMGENCONF1_PWM2CTRL 4 +#define REG_PWMGENCONF1_PWM1NCTRL 3 +#define REG_PWMGENCONF1_PWM1PCTRL 2 +#define REG_PWMGENCONF1_PWM2NCTRL 1 +#define REG_PWMGENCONF1_PWM2PCTRL 0 + +/* REG_PWMGENCONF2 */ +/* REG_PWMGENCONF3 */ +/* REG_PWMGENCONF4 */ +/* REG_PWMGENCONF5 */ +#define REG_PWMGENCONFX_PWMVIBXPOL 7 +#define REG_PWMGENCONFX_PWMVIBXDUTCYC 0 +#define REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX 0x64 + +/* REG_ANAGAIN1 */ +/* REG_ANAGAIN2 */ +#define REG_ANAGAINX_ENSEMICX 7 +#define REG_ANAGAINX_LOWPOWMICX 6 +#define REG_ANAGAINX_MICXGAIN 0 +#define REG_ANAGAINX_MICXGAIN_MAX 0x1F + +/* REG_ANAGAIN3 */ +#define REG_ANAGAIN3_HSLGAIN 4 +#define REG_ANAGAIN3_HSRGAIN 0 +#define REG_ANAGAIN3_HSXGAIN_MAX 0x0F + +/* REG_ANAGAIN4 */ +#define REG_ANAGAIN4_LINLGAIN 4 +#define REG_ANAGAIN4_LINRGAIN 0 +#define REG_ANAGAIN4_LINXGAIN_MAX 0x0F + +/* REG_DIGLINHSLGAIN */ +/* REG_DIGLINHSRGAIN */ +#define REG_DIGLINHSXGAIN_LINTOHSXGAIN 0 +#define REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX 0x13 + +/* REG_ADFILTCONF */ +#define REG_ADFILTCONF_AD1NH 7 +#define REG_ADFILTCONF_AD2NH 6 +#define REG_ADFILTCONF_AD3NH 5 +#define REG_ADFILTCONF_AD4NH 4 +#define REG_ADFILTCONF_AD1VOICE 3 +#define REG_ADFILTCONF_AD2VOICE 2 +#define REG_ADFILTCONF_AD3VOICE 1 +#define REG_ADFILTCONF_AD4VOICE 0 + +/* REG_DIGIFCONF1 */ +#define REG_DIGIFCONF1_ENMASTGEN 7 +#define REG_DIGIFCONF1_IF1BITCLKOS1 6 +#define REG_DIGIFCONF1_IF1BITCLKOS0 5 +#define REG_DIGIFCONF1_ENFSBITCLK1 4 +#define REG_DIGIFCONF1_IF0BITCLKOS1 2 +#define REG_DIGIFCONF1_IF0BITCLKOS0 1 +#define REG_DIGIFCONF1_ENFSBITCLK0 0 + +/* REG_DIGIFCONF2 */ +#define REG_DIGIFCONF2_FSYNC0P 6 +#define REG_DIGIFCONF2_BITCLK0P 5 +#define REG_DIGIFCONF2_IF0DEL 4 +#define REG_DIGIFCONF2_IF0FORMAT1 3 +#define REG_DIGIFCONF2_IF0FORMAT0 2 +#define REG_DIGIFCONF2_IF0WL1 1 +#define REG_DIGIFCONF2_IF0WL0 0 + +/* REG_DIGIFCONF3 */ +#define REG_DIGIFCONF3_IF0DATOIF1AD 7 +#define REG_DIGIFCONF3_IF0CLKTOIF1CLK 6 +#define REG_DIGIFCONF3_IF1MASTER 5 +#define REG_DIGIFCONF3_IF1DATOIF0AD 3 +#define REG_DIGIFCONF3_IF1CLKTOIF0CLK 2 +#define REG_DIGIFCONF3_IF0MASTER 1 +#define REG_DIGIFCONF3_IF0BFIFOEN 0 + +/* REG_DIGIFCONF4 */ +#define REG_DIGIFCONF4_FSYNC1P 6 +#define REG_DIGIFCONF4_BITCLK1P 5 +#define REG_DIGIFCONF4_IF1DEL 4 +#define REG_DIGIFCONF4_IF1FORMAT1 3 +#define REG_DIGIFCONF4_IF1FORMAT0 2 +#define REG_DIGIFCONF4_IF1WL1 1 +#define REG_DIGIFCONF4_IF1WL0 0 + +/* REG_ADSLOTSELX */ +#define REG_ADSLOTSELX_AD_OUT1_TO_SLOT_ODD 0x00 +#define REG_ADSLOTSELX_AD_OUT2_TO_SLOT_ODD 0x01 +#define REG_ADSLOTSELX_AD_OUT3_TO_SLOT_ODD 0x02 +#define REG_ADSLOTSELX_AD_OUT4_TO_SLOT_ODD 0x03 +#define REG_ADSLOTSELX_AD_OUT5_TO_SLOT_ODD 0x04 +#define REG_ADSLOTSELX_AD_OUT6_TO_SLOT_ODD 0x05 +#define REG_ADSLOTSELX_AD_OUT7_TO_SLOT_ODD 0x06 +#define REG_ADSLOTSELX_AD_OUT8_TO_SLOT_ODD 0x07 +#define REG_ADSLOTSELX_ZEROES_TO_SLOT_ODD 0x08 +#define REG_ADSLOTSELX_TRISTATE_TO_SLOT_ODD 0x0F +#define REG_ADSLOTSELX_AD_OUT1_TO_SLOT_EVEN 0x00 +#define REG_ADSLOTSELX_AD_OUT2_TO_SLOT_EVEN 0x10 +#define REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN 0x20 +#define REG_ADSLOTSELX_AD_OUT4_TO_SLOT_EVEN 0x30 +#define REG_ADSLOTSELX_AD_OUT5_TO_SLOT_EVEN 0x40 +#define REG_ADSLOTSELX_AD_OUT6_TO_SLOT_EVEN 0x50 +#define REG_ADSLOTSELX_AD_OUT7_TO_SLOT_EVEN 0x60 +#define REG_ADSLOTSELX_AD_OUT8_TO_SLOT_EVEN 0x70 +#define REG_ADSLOTSELX_ZEROES_TO_SLOT_EVEN 0x80 +#define REG_ADSLOTSELX_TRISTATE_TO_SLOT_EVEN 0xF0 +#define REG_ADSLOTSELX_EVEN_SHIFT 0 +#define REG_ADSLOTSELX_ODD_SHIFT 4 + +/* REG_ADSLOTHIZCTRL1 */ +/* REG_ADSLOTHIZCTRL2 */ +/* REG_ADSLOTHIZCTRL3 */ +/* REG_ADSLOTHIZCTRL4 */ +/* REG_DASLOTCONF1 */ +#define REG_DASLOTCONF1_DA12VOICE 7 +#define REG_DASLOTCONF1_SWAPDA12_34 6 +#define REG_DASLOTCONF1_DAI7TOADO1 5 + +/* REG_DASLOTCONF2 */ +#define REG_DASLOTCONF2_DAI8TOADO2 5 + +/* REG_DASLOTCONF3 */ +#define REG_DASLOTCONF3_DA34VOICE 7 +#define REG_DASLOTCONF3_DAI7TOADO3 5 + +/* REG_DASLOTCONF4 */ +#define REG_DASLOTCONF4_DAI8TOADO4 5 + +/* REG_DASLOTCONF5 */ +#define REG_DASLOTCONF5_DA56VOICE 7 +#define REG_DASLOTCONF5_DAI7TOADO5 5 + +/* REG_DASLOTCONF6 */ +#define REG_DASLOTCONF6_DAI8TOADO6 5 + +/* REG_DASLOTCONF7 */ +#define REG_DASLOTCONF7_DAI8TOADO7 5 + +/* REG_DASLOTCONF8 */ +#define REG_DASLOTCONF8_DAI7TOADO8 5 + +#define REG_DASLOTCONFX_SLTODAX_SHIFT 0 +#define REG_DASLOTCONFX_SLTODAX_MASK 0x1F + +/* REG_CLASSDCONF1 */ +#define REG_CLASSDCONF1_PARLHF 7 +#define REG_CLASSDCONF1_PARLVIB 6 +#define REG_CLASSDCONF1_VIB1SWAPEN 3 +#define REG_CLASSDCONF1_VIB2SWAPEN 2 +#define REG_CLASSDCONF1_HFLSWAPEN 1 +#define REG_CLASSDCONF1_HFRSWAPEN 0 + +/* REG_CLASSDCONF2 */ +#define REG_CLASSDCONF2_FIRBYP3 7 +#define REG_CLASSDCONF2_FIRBYP2 6 +#define REG_CLASSDCONF2_FIRBYP1 5 +#define REG_CLASSDCONF2_FIRBYP0 4 +#define REG_CLASSDCONF2_HIGHVOLEN3 3 +#define REG_CLASSDCONF2_HIGHVOLEN2 2 +#define REG_CLASSDCONF2_HIGHVOLEN1 1 +#define REG_CLASSDCONF2_HIGHVOLEN0 0 + +/* REG_CLASSDCONF3 */ +#define REG_CLASSDCONF3_DITHHPGAIN 4 +#define REG_CLASSDCONF3_DITHHPGAIN_MAX 0x0A +#define REG_CLASSDCONF3_DITHWGAIN 0 +#define REG_CLASSDCONF3_DITHWGAIN_MAX 0x0A + +/* REG_DMICFILTCONF */ +#define REG_DMICFILTCONF_ANCINSEL 7 +#define REG_DMICFILTCONF_DA3TOEAR 6 +#define REG_DMICFILTCONF_DMIC1SINC3 5 +#define REG_DMICFILTCONF_DMIC2SINC3 4 +#define REG_DMICFILTCONF_DMIC3SINC3 3 +#define REG_DMICFILTCONF_DMIC4SINC3 2 +#define REG_DMICFILTCONF_DMIC5SINC3 1 +#define REG_DMICFILTCONF_DMIC6SINC3 0 + +/* REG_DIGMULTCONF1 */ +#define REG_DIGMULTCONF1_DATOHSLEN 7 +#define REG_DIGMULTCONF1_DATOHSREN 6 +#define REG_DIGMULTCONF1_AD1SEL 5 +#define REG_DIGMULTCONF1_AD2SEL 4 +#define REG_DIGMULTCONF1_AD3SEL 3 +#define REG_DIGMULTCONF1_AD5SEL 2 +#define REG_DIGMULTCONF1_AD6SEL 1 +#define REG_DIGMULTCONF1_ANCSEL 0 + +/* REG_DIGMULTCONF2 */ +#define REG_DIGMULTCONF2_DATOHFREN 7 +#define REG_DIGMULTCONF2_DATOHFLEN 6 +#define REG_DIGMULTCONF2_HFRSEL 5 +#define REG_DIGMULTCONF2_HFLSEL 4 +#define REG_DIGMULTCONF2_FIRSID1SEL 2 +#define REG_DIGMULTCONF2_FIRSID2SEL 0 + +/* REG_ADDIGGAIN1 */ +/* REG_ADDIGGAIN2 */ +/* REG_ADDIGGAIN3 */ +/* REG_ADDIGGAIN4 */ +/* REG_ADDIGGAIN5 */ +/* REG_ADDIGGAIN6 */ +#define REG_ADDIGGAINX_FADEDISADX 6 +#define REG_ADDIGGAINX_ADXGAIN_MAX 0x3F + +/* REG_DADIGGAIN1 */ +/* REG_DADIGGAIN2 */ +/* REG_DADIGGAIN3 */ +/* REG_DADIGGAIN4 */ +/* REG_DADIGGAIN5 */ +/* REG_DADIGGAIN6 */ +#define REG_DADIGGAINX_FADEDISDAX 6 +#define REG_DADIGGAINX_DAXGAIN_MAX 0x3F + +/* REG_ADDIGLOOPGAIN1 */ +/* REG_ADDIGLOOPGAIN2 */ +#define REG_ADDIGLOOPGAINX_FADEDISADXL 6 +#define REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX 0x3F + +/* REG_HSLEARDIGGAIN */ +#define REG_HSLEARDIGGAIN_HSSINC1 7 +#define REG_HSLEARDIGGAIN_FADEDISHSL 4 +#define REG_HSLEARDIGGAIN_HSLDGAIN_MAX 0x09 + +/* REG_HSRDIGGAIN */ +#define REG_HSRDIGGAIN_FADESPEED 6 +#define REG_HSRDIGGAIN_FADEDISHSR 4 +#define REG_HSRDIGGAIN_HSRDGAIN_MAX 0x09 + +/* REG_SIDFIRGAIN1 */ +/* REG_SIDFIRGAIN2 */ +#define REG_SIDFIRGAINX_FIRSIDXGAIN_MAX 0x1F + +/* REG_ANCCONF1 */ +#define REG_ANCCONF1_ANCIIRUPDATE 3 +#define REG_ANCCONF1_ENANC 2 +#define REG_ANCCONF1_ANCIIRINIT 1 +#define REG_ANCCONF1_ANCFIRUPDATE 0 + +/* REG_ANCCONF2 */ +#define REG_ANCCONF2_VALUE_MIN -0x10 +#define REG_ANCCONF2_VALUE_MAX 0x0F +/* REG_ANCCONF3 */ +#define REG_ANCCONF3_VALUE_MIN -0x10 +#define REG_ANCCONF3_VALUE_MAX 0x0F +/* REG_ANCCONF4 */ +#define REG_ANCCONF4_VALUE_MIN -0x10 +#define REG_ANCCONF4_VALUE_MAX 0x0F +/* REG_ANC_FIR_COEFFS */ +#define REG_ANC_FIR_COEFF_MIN -0x8000 +#define REG_ANC_FIR_COEFF_MAX 0x7FFF +#define REG_ANC_FIR_COEFFS 0xF +/* REG_ANC_IIR_COEFFS */ +#define REG_ANC_IIR_COEFF_MIN -0x800000 +#define REG_ANC_IIR_COEFF_MAX 0x7FFFFF +#define REG_ANC_IIR_COEFFS 0x18 +/* REG_ANC_WARP_DELAY */ +#define REG_ANC_WARP_DELAY_MIN 0x0000 +#define REG_ANC_WARP_DELAY_MAX 0xFFFF +/* REG_ANCCONF11 */ +/* REG_ANCCONF12 */ +/* REG_ANCCONF13 */ +/* REG_ANCCONF14 */ + +/* REG_SIDFIRADR */ +#define REG_SIDFIRADR_FIRSIDSET 7 +#define REG_SIDFIRADR_ADDRESS_SHIFT 0 +#define REG_SIDFIRADR_ADDRESS_MAX 0x7F + +/* REG_SIDFIRCOEF1 */ +/* REG_SIDFIRCOEF2 */ +#define REG_SID_FIR_COEFF_MIN 0 +#define REG_SID_FIR_COEFF_MAX 0xFFFF +#define REG_SID_FIR_COEFFS 128 + +/* REG_SIDFIRCONF */ +#define REG_SIDFIRCONF_ENFIRSIDS 2 +#define REG_SIDFIRCONF_FIRSIDSTOIF1 1 +#define REG_SIDFIRCONF_FIRSIDBUSY 0 + +/* REG_AUDINTMASK1 */ +/* REG_AUDINTSOURCE1 */ +/* REG_AUDINTMASK2 */ +/* REG_AUDINTSOURCE2 */ + +/* REG_FIFOCONF1 */ +#define REG_FIFOCONF1_BFIFOMASK 0x80 +#define REG_FIFOCONF1_BFIFO19M2 0x40 +#define REG_FIFOCONF1_BFIFOINT_SHIFT 0 +#define REG_FIFOCONF1_BFIFOINT_MAX 0x3F + +/* REG_FIFOCONF2 */ +#define REG_FIFOCONF2_BFIFOTX_SHIFT 0 +#define REG_FIFOCONF2_BFIFOTX_MAX 0xFF + +/* REG_FIFOCONF3 */ +#define REG_FIFOCONF3_BFIFOEXSL_SHIFT 5 +#define REG_FIFOCONF3_BFIFOEXSL_MAX 0x5 +#define REG_FIFOCONF3_PREBITCLK0_SHIFT 2 +#define REG_FIFOCONF3_PREBITCLK0_MAX 0x7 +#define REG_FIFOCONF3_BFIFOMAST_SHIFT 1 +#define REG_FIFOCONF3_BFIFORUN_SHIFT 0 + +/* REG_FIFOCONF4 */ +#define REG_FIFOCONF4_BFIFOFRAMSW_SHIFT 0 +#define REG_FIFOCONF4_BFIFOFRAMSW_MAX 0xFF + +/* REG_FIFOCONF5 */ +#define REG_FIFOCONF5_BFIFOWAKEUP_SHIFT 0 +#define REG_FIFOCONF5_BFIFOWAKEUP_MAX 0xFF + +/* REG_FIFOCONF6 */ +#define REG_FIFOCONF6_BFIFOSAMPLE_SHIFT 0 +#define REG_FIFOCONF6_BFIFOSAMPLE_MAX 0xFF + +/* REG_AUDREV */ + +#endif diff --git a/sound/soc/codecs/av8100_audio.c b/sound/soc/codecs/av8100_audio.c new file mode 100644 index 00000000000..cf3d3fc489c --- /dev/null +++ b/sound/soc/codecs/av8100_audio.c @@ -0,0 +1,527 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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 <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <video/av8100.h> +#include <video/hdmi.h> + +#include "av8100_audio.h" + +/* codec private data */ +struct av8100_codec_dai_data { + struct hdmi_audio_settings as; +}; + +static struct av8100_codec_dai_data *get_dai_data_codec(struct snd_soc_codec *codec, + int dai_id) +{ + struct av8100_codec_dai_data *dai_data = snd_soc_codec_get_drvdata(codec); + return &dai_data[dai_id]; +} + +static struct av8100_codec_dai_data *get_dai_data(struct snd_soc_dai *codec_dai) +{ + return get_dai_data_codec(codec_dai->codec, codec_dai->id); +} + +/* Controls - Non-DAPM Non-ASoC */ + +/* Coding Type */ + +static const char *hdmi_coding_type_str[] = {"AV8100_CODEC_CT_REFER", + "AV8100_CODEC_CT_IEC60958_PCM", + "AV8100_CODEC_CT_AC3", + "AV8100_CODEC_CT_MPEG1", + "AV8100_CODEC_CT_MP3", + "AV8100_CODEC_CT_MPEG2", + "AV8100_CODEC_CT_AAC", + "AV8100_CODEC_CT_DTS", + "AV8100_CODEC_CT_ATRAC", + "AV8100_CODEC_CT_ONE_BIT_AUDIO", + "AV8100_CODEC_CT_DOLBY_DIGITAL", + "AV8100_CODEC_CT_DTS_HD", + "AV8100_CODEC_CT_MAT", + "AV8100_CODEC_CT_DST", + "AV8100_CODEC_CT_WMA_PRO"}; + +enum hdmi_audio_coding_type audio_coding_type; + +static int hdmi_coding_type_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int items = ARRAY_SIZE(hdmi_coding_type_str); + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + + if (uinfo->value.enumerated.item > items - 1) + uinfo->value.enumerated.item = items - 1; + + strcpy(uinfo->value.enumerated.name, + hdmi_coding_type_str[uinfo->value.enumerated.item]); + + return 0; +} + +static int hdmi_coding_type_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = audio_coding_type; + + return 0; +} + +static int hdmi_coding_type_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int items = ARRAY_SIZE(hdmi_coding_type_str); + + if (ucontrol->value.enumerated.item[0] > items - 1) + ucontrol->value.enumerated.item[0] = items - 1; + + audio_coding_type = ucontrol->value.enumerated.item[0]; + + return 1; +} + +static const struct snd_kcontrol_new hdmi_coding_type_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Coding Type", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = hdmi_coding_type_control_info, + .get = hdmi_coding_type_control_get, + .put = hdmi_coding_type_control_put, +}; + +/* Extended interface for codec-driver */ + +int av8100_audio_change_hdmi_audio_settings(struct snd_soc_dai *codec_dai, + struct hdmi_audio_settings *as) +{ + struct av8100_codec_dai_data *dai_data = get_dai_data(codec_dai); + + pr_debug("%s: Enter.\n", __func__); + + dai_data->as.audio_channel_count = as->audio_channel_count; + dai_data->as.sampling_frequency = as->sampling_frequency; + dai_data->as.sample_size = as->sample_size; + dai_data->as.channel_allocation = as->channel_allocation; + dai_data->as.level_shift_value = as->level_shift_value; + dai_data->as.downmix_inhibit = as->downmix_inhibit; + + return 0; +} + +static int av8100_codec_powerup(void) +{ + struct av8100_status status; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + pr_debug("%s: Powering up AV8100.", __func__); + ret = av8100_powerup(); + if (ret != 0) { + pr_err("%s: Power up AV8100 failed " + "(av8100_powerup returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + } + if (status.av8100_state < AV8100_OPMODE_INIT) { + ret = av8100_download_firmware(I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Download firmware failed " + "(av8100_download_firmware returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + } + + return 0; +} + +static int av8100_codec_setup_hdmi_format(void) +{ + union av8100_configuration config; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + pr_debug("%s: hdmi_mode = AV8100_HDMI_ON.", __func__); + pr_debug("%s: hdmi_format = AV8100_HDMI.", __func__); + config.hdmi_format.hdmi_mode = AV8100_HDMI_ON; + config.hdmi_format.hdmi_format = AV8100_HDMI; + ret = av8100_conf_prep(AV8100_COMMAND_HDMI, &config); + if (ret != 0) { + pr_err("%s: Setting hdmi_format failed " + "(av8100_conf_prep returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + ret = av8100_conf_w(AV8100_COMMAND_HDMI, + NULL, + NULL, + I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Setting hdmi_format failed " + "(av8100_conf_w returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + + return 0; +} + +static int av8100_codec_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static int av8100_codec_send_audio_infoframe(struct hdmi_audio_settings *as) +{ + union av8100_configuration config; + struct av8100_infoframes_format_cmd info_fr; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + pr_debug("%s: HDMI-settings:\n", __func__); + pr_debug("%s: audio_coding_type = %d\n", __func__, audio_coding_type); + pr_debug("%s: audio_channel_count = %d\n", __func__, as->audio_channel_count); + pr_debug("%s: sampling_frequency = %d\n", __func__, as->sampling_frequency); + pr_debug("%s: sample_size = %d\n", __func__, as->sample_size); + pr_debug("%s: channel_allocation = %d\n", __func__, as->channel_allocation); + pr_debug("%s: level_shift_value = %d\n", __func__, as->level_shift_value); + pr_debug("%s: downmix_inhibit = %d\n", __func__, as->downmix_inhibit); + + /* Prepare the infoframe from the hdmi_audio_settings struct */ + pr_info("%s: Preparing audio info-frame.", __func__); + info_fr.type = 0x84; + info_fr.version = 0x01; + info_fr.length = 0x0a; + info_fr.data[0] = (audio_coding_type << 4) | as->audio_channel_count; + info_fr.data[1] = (as->sampling_frequency << 2) | as->sample_size; + info_fr.data[2] = 0; + info_fr.data[3] = as->channel_allocation; + info_fr.data[4] = ((int)as->downmix_inhibit << 7) | + (as->level_shift_value << 3); + info_fr.data[5] = 0; + info_fr.data[6] = 0; + info_fr.data[7] = 0; + info_fr.data[8] = 0; + info_fr.data[9] = 0; + info_fr.crc = 0x100 - (info_fr.type + + info_fr.version + + info_fr.length + + info_fr.data[0] + + info_fr.data[1] + + info_fr.data[3] + + info_fr.data[4]); + config.infoframes_format.type = info_fr.type; + config.infoframes_format.version = info_fr.version; + config.infoframes_format.crc = info_fr.crc; + config.infoframes_format.length = info_fr.length; + memcpy(&config.infoframes_format.data, info_fr.data, info_fr.length); + + /* Send audio info-frame */ + pr_info("%s: Sending audio info-frame.", __func__); + ret = av8100_conf_prep(AV8100_COMMAND_INFOFRAMES, &config); + if (ret != 0) { + pr_err("%s: Sending audio info-frame failed " + "(av8100_conf_prep returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + ret = av8100_conf_w(AV8100_COMMAND_INFOFRAMES, + NULL, + NULL, + I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Sending audio info-frame failed " + "(av8100_conf_w returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + + return 0; +} + +static int av8100_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *codec_dai) +{ + struct av8100_codec_dai_data *dai_data = get_dai_data(codec_dai); + + pr_debug("%s: Enter.\n", __func__); + + av8100_codec_send_audio_infoframe(&dai_data->as); + + return 0; +} + +static int av8100_codec_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + /* Startup AV8100 if it is not already started */ + ret = av8100_codec_powerup(); + if (ret != 0) { + pr_err("%s: Startup of AV8100 failed " + "(av8100_codec_powerupAV8100 returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + + return 0; +} + +static void av8100_codec_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + pr_debug("%s: Enter.\n", __func__); +} + +static int av8100_codec_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, + unsigned int freq, int dir) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static int av8100_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + union av8100_configuration config; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + /* Set the HDMI format of AV8100 */ + ret = av8100_codec_setup_hdmi_format(); + if (ret != 0) + return ret; + + /* Set the audio input format of AV8100 */ + config.audio_input_format.audio_input_if_format = + ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) ? + AV8100_AUDIO_TDM_MODE : AV8100_AUDIO_I2SDELAYED_MODE; + config.audio_input_format.audio_if_mode = + ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) ? + AV8100_AUDIO_MASTER : AV8100_AUDIO_SLAVE; + pr_info("%s: Setting audio_input_format " + "(if_format = %d, if_mode = %d).", + __func__, + config.audio_input_format.audio_input_if_format, + config.audio_input_format.audio_if_mode); + config.audio_input_format.i2s_input_nb = 1; + config.audio_input_format.sample_audio_freq = AV8100_AUDIO_FREQ_48KHZ; + config.audio_input_format.audio_word_lg = AV8100_AUDIO_16BITS; + config.audio_input_format.audio_format = AV8100_AUDIO_LPCM_MODE; + config.audio_input_format.audio_mute = AV8100_AUDIO_MUTE_DISABLE; + ret = av8100_conf_prep(AV8100_COMMAND_AUDIO_INPUT_FORMAT, &config); + if (ret != 0) { + pr_err("%s: Setting audio_input_format failed " + "(av8100_conf_prep returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + ret = av8100_conf_w(AV8100_COMMAND_AUDIO_INPUT_FORMAT, + NULL, + NULL, + I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Setting audio_input_format failed " + "(av8100_conf_w returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + + return 0; +} + +struct snd_soc_dai_driver av8100_dai_driver = { + .name = "av8100-codec-dai", + .playback = { + .stream_name = "AV8100 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = AV8100_SUPPORTED_RATE, + .formats = AV8100_SUPPORTED_FMT, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .prepare = av8100_codec_pcm_prepare, + .hw_params = av8100_codec_pcm_hw_params, + .startup = av8100_codec_pcm_startup, + .shutdown = av8100_codec_pcm_shutdown, + .set_sysclk = av8100_codec_set_dai_sysclk, + .set_fmt = av8100_codec_set_dai_fmt, + } + }, +}; +EXPORT_SYMBOL_GPL(av8100_dai_driver); + +static int av8100_codec_probe(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + audio_coding_type = AV8100_CODEC_CT_REFER; + + /* Add controls with events */ + snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&hdmi_coding_type_control, codec)); + + return 0; +} + +static int av8100_codec_remove(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +static int av8100_codec_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +static int av8100_codec_resume(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +struct snd_soc_codec_driver av8100_codec_drv = { + .probe = av8100_codec_probe, + .remove = av8100_codec_remove, + .suspend = av8100_codec_suspend, + .resume = av8100_codec_resume +}; + +static __devinit int av8100_codec_drv_probe(struct platform_device *pdev) +{ + struct av8100_codec_dai_data *dai_data; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + pr_info("%s: Init codec private data..\n", __func__); + dai_data = kzalloc(sizeof(struct av8100_codec_dai_data), GFP_KERNEL); + if (dai_data == NULL) + return -ENOMEM; + + /* Setup hdmi_audio_settings default values */ + dai_data[0].as.audio_channel_count = AV8100_CODEC_CC_2CH; + dai_data[0].as.sampling_frequency = AV8100_CODEC_SF_48KHZ; + dai_data[0].as.sample_size = AV8100_CODEC_SS_16BIT; + dai_data[0].as.channel_allocation = AV8100_CODEC_CA_FL_FR; + dai_data[0].as.level_shift_value = AV8100_CODEC_LSV_0DB; + dai_data[0].as.downmix_inhibit = false; + + platform_set_drvdata(pdev, dai_data); + + pr_info("%s: Register codec.\n", __func__); + ret = snd_soc_register_codec(&pdev->dev, &av8100_codec_drv, &av8100_dai_driver, 1); + if (ret < 0) { + pr_debug("%s: Error: Failed to register codec (ret = %d).\n", + __func__, + ret); + return ret; + } + + return 0; +} + +static int __devexit av8100_codec_drv_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + return 0; +} + +static const struct platform_device_id av8100_codec_platform_id[] = { + { "av8100-codec", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, av8100_codec_platform_id); + +static struct platform_driver av8100_codec_platform_driver = { + .driver = { + .name = "av8100-codec", + .owner = THIS_MODULE, + }, + .probe = av8100_codec_drv_probe, + .remove = __devexit_p(av8100_codec_drv_remove), + .id_table = av8100_codec_platform_id, +}; + +static int __devinit av8100_codec_platform_drv_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + ret = platform_driver_register(&av8100_codec_platform_driver); + if (ret != 0) { + pr_err("Failed to register AV8100 platform driver (%d)!\n", ret); + } + + return ret; +} + +static void __exit av8100_codec_platform_drv_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + platform_driver_unregister(&av8100_codec_platform_driver); +} + +module_init(av8100_codec_platform_drv_init); +module_exit(av8100_codec_platform_drv_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/av8100_audio.h b/sound/soc/codecs/av8100_audio.h new file mode 100644 index 00000000000..4802e0d0242 --- /dev/null +++ b/sound/soc/codecs/av8100_audio.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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. + */ +#ifndef AV8100_AUDIO_CODEC_H +#define AV8100_AUDIO_CODEC_H + +/* Supported sampling rates */ +#define AV8100_SUPPORTED_RATE (SNDRV_PCM_RATE_48000) + +/* Supported data formats */ +#define AV8100_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE) + +/* TDM-slot mask */ +#define AV8100_CODEC_MASK_MONO 0x0001 +#define AV8100_CODEC_MASK_STEREO 0x0005 +#define AV8100_CODEC_MASK_2DOT1 0x0015 +#define AV8100_CODEC_MASK_QUAD 0x0505 +#define AV8100_CODEC_MASK_5DOT0 0x0545 +#define AV8100_CODEC_MASK_5DOT1 0x0555 +#define AV8100_CODEC_MASK_7DOT0 0x5545 +#define AV8100_CODEC_MASK_7DOT1 0x5555 + +enum hdmi_audio_coding_type { + AV8100_CODEC_CT_REFER, + AV8100_CODEC_CT_IEC60958_PCM, + AV8100_CODEC_CT_AC3, + AV8100_CODEC_CT_MPEG1, + AV8100_CODEC_CT_MP3, + AV8100_CODEC_CT_MPEG2, + AV8100_CODEC_CT_AAC, + AV8100_CODEC_CT_DTS, + AV8100_CODEC_CT_ATRAC, + AV8100_CODEC_CT_ONE_BIT_AUDIO, + AV8100_CODEC_CT_DOLBY_DIGITAL, + AV8100_CODEC_CT_DTS_HD, + AV8100_CODEC_CT_MAT, + AV8100_CODEC_CT_DST, + AV8100_CODEC_CT_WMA_PRO +}; + +enum hdmi_audio_channel_count { + AV8100_CODEC_CC_REFER, + AV8100_CODEC_CC_2CH, + AV8100_CODEC_CC_3CH, + AV8100_CODEC_CC_4CH, + AV8100_CODEC_CC_5CH, + AV8100_CODEC_CC_6CH, + AV8100_CODEC_CC_7CH, + AV8100_CODEC_CC_8CH +}; + +enum hdmi_sampling_frequency { + AV8100_CODEC_SF_REFER, + AV8100_CODEC_SF_32KHZ, + AV8100_CODEC_SF_44_1KHZ, + AV8100_CODEC_SF_48KHZ, + AV8100_CODEC_SF_88_2KHZ, + AV8100_CODEC_SF_96KHZ, + AV8100_CODEC_SF_176_4KHZ, + AV8100_CODEC_SF_192KHZ +}; + +enum hdmi_sample_size { + AV8100_CODEC_SS_REFER, + AV8100_CODEC_SS_16BIT, + AV8100_CODEC_SS_20BIT, + AV8100_CODEC_SS_24BIT +}; + +enum hdmi_speaker_placement { + AV8100_CODEC_SP_FL, /* Front Left */ + AV8100_CODEC_SP_FC, /* Front Center */ + AV8100_CODEC_SP_FR, /* Front Right */ + AV8100_CODEC_SP_FLC, /* Front Left Center */ + AV8100_CODEC_SP_FRC, /* Front Right Center */ + AV8100_CODEC_SP_RL, /* Rear Left */ + AV8100_CODEC_SP_RC, /* Rear Center */ + AV8100_CODEC_SP_RR, /* Rear Right */ + AV8100_CODEC_SP_RLC, /* Rear Left Center */ + AV8100_CODEC_SP_RRC, /* Rear Right Center */ + AV8100_CODEC_SP_LFE, /* Low Frequency Effekt */ +}; + +enum hdmi_channel_allocation { + AV8100_CODEC_CA_FL_FR, /* 0x00, Stereo */ + AV8100_CODEC_CA_FL_FR_LFE, /* 0x01, 2.1 */ + AV8100_CODEC_CA_FL_FR_FC, /* 0x02*/ + AV8100_CODEC_CA_FL_FR_LFE_FC, /* 0x03*/ + AV8100_CODEC_CA_FL_FR_RC, /* 0x04*/ + AV8100_CODEC_CA_FL_FR_LFE_RC, /* 0x05*/ + AV8100_CODEC_CA_FL_FR_FC_RC, /* 0x06*/ + AV8100_CODEC_CA_FL_FR_LFE_FC_RC, /* 0x07*/ + AV8100_CODEC_CA_FL_FR_RL_RR, /* 0x08, Quad */ + AV8100_CODEC_CA_FL_FR_LFE_RL_RR, /* 0x09*/ + AV8100_CODEC_CA_FL_FR_FC_RL_RR, /* 0x0a, 5.0*/ + AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR, /* 0x0b, 5.1*/ + AV8100_CODEC_CA_FL_FR_RL_RR_RC, /* 0x0c*/ + AV8100_CODEC_CA_FL_FR_LFE_RL_RR_RC, /* 0x0d*/ + AV8100_CODEC_CA_FL_FR_RC_RL_RR_RC, /* 0x0e*/ + AV8100_CODEC_CA_FL_FR_LFE_RC_RL_RR_RC, /* 0x0f*/ + AV8100_CODEC_CA_FL_FR_RL_RR_RLC_RRC, /* 0x10*/ + AV8100_CODEC_CA_FL_FR_LFE_RL_RR_RLC_RRC, /* 0x11*/ + AV8100_CODEC_CA_FL_FR_FC_RL_RR_RLC_RRC, /* 0x12*/ + AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC, /* 0x13*/ + AV8100_CODEC_CA_FL_FR_FLC_FRC, /* 0x14*/ + AV8100_CODEC_CA_FL_FR_LFE_FLC_FRC, /* 0x15*/ + AV8100_CODEC_CA_FL_FR_FC_FLC_FRC, /* 0x16*/ + AV8100_CODEC_CA_FL_FR_LFE_FC_FLC_FRC, /* 0x17*/ + AV8100_CODEC_CA_FL_FR_RC_FLC_FRC, /* 0x18*/ + AV8100_CODEC_CA_FL_FR_LFE_RC_FLC_FRC, /* 0x19*/ + AV8100_CODEC_CA_FL_FR_FC_RC_FLC_FRC, /* 0x1a*/ + AV8100_CODEC_CA_FL_FR_LFE_FR_FC_RC_FLC_FRC, /* 0x1b*/ + AV8100_CODEC_CA_FL_FR_RL_RR_FLC_FRC, /* 0x1c*/ + AV8100_CODEC_CA_FL_FR_LFE_RL_RR_FLC_FRC, /* 0x1d*/ + AV8100_CODEC_CA_FL_FR_FC_RL_RR_FLC_FRC, /* 0x1e*/ + AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_FLC_FRC /* 0x1f, 7.1 */ +}; + +enum hdmi_level_shift_value { + AV8100_CODEC_LSV_0DB, + AV8100_CODEC_LSV_1DB, + AV8100_CODEC_LSV_2DB, + AV8100_CODEC_LSV_3DB, + AV8100_CODEC_LSV_4DB, + AV8100_CODEC_LSV_5DB, + AV8100_CODEC_LSV_6DB, + AV8100_CODEC_LSV_7DB, + AV8100_CODEC_LSV_8DB, + AV8100_CODEC_LSV_9DB, + AV8100_CODEC_LSV_10DB, + AV8100_CODEC_LSV_11DB, + AV8100_CODEC_LSV_12DB, + AV8100_CODEC_LSV_13DB, + AV8100_CODEC_LSV_14DB, + AV8100_CODEC_LSV_15DB +}; + +struct hdmi_audio_settings { + enum hdmi_audio_channel_count audio_channel_count; + enum hdmi_sampling_frequency sampling_frequency; + enum hdmi_sample_size sample_size; + enum hdmi_channel_allocation channel_allocation; + enum hdmi_level_shift_value level_shift_value; + bool downmix_inhibit; +}; + +/* Extended interface for codec-driver */ +int av8100_audio_change_hdmi_audio_settings(struct snd_soc_dai *dai, + struct hdmi_audio_settings *as); + +#endif /* AV8100_AUDIO_CODEC_H */ + + + diff --git a/sound/soc/codecs/cg29xx.c b/sound/soc/codecs/cg29xx.c new file mode 100644 index 00000000000..109387fa2f9 --- /dev/null +++ b/sound/soc/codecs/cg29xx.c @@ -0,0 +1,772 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Roger Nilsson <roger.xr.nilsson@stericsson.com>, + * Ola Lilja <ola.o.lilja@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 <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 <linux/bitops.h> +#include <../../../drivers/staging/cg2900/include/cg2900_audio.h> + +#include "cg29xx.h" + +#define CG29XX_NBR_OF_DAI 2 +#define CG29XX_SUPPORTED_RATE_PCM (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000) + +#define CG29XX_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define CG29XX_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE) + +enum cg29xx_dai_direction { + CG29XX_DAI_DIRECTION_TX, + CG29XX_DAI_DIRECTION_RX +}; + +static int cg29xx_dai_startup( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +static int cg29xx_dai_prepare( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +static int cg29xx_dai_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai); + +static void cg29xx_dai_shutdown( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +static int cg29xx_set_dai_sysclk( + struct snd_soc_dai *codec_dai, + int clk_id, + unsigned int freq, int dir); + +static int cg29xx_set_dai_fmt( + struct snd_soc_dai *codec_dai, + unsigned int fmt); + +static int cg29xx_set_tdm_slot( + struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, + int slot_width); + +static struct cg29xx_codec codec_private = { + .session = 0, +}; + +static struct snd_soc_dai_ops cg29xx_dai_driver_dai_ops = { + .startup = cg29xx_dai_startup, + .prepare = cg29xx_dai_prepare, + .hw_params = cg29xx_dai_hw_params, + .shutdown = cg29xx_dai_shutdown, + .set_sysclk = cg29xx_set_dai_sysclk, + .set_fmt = cg29xx_set_dai_fmt, + .set_tdm_slot = cg29xx_set_tdm_slot +}; + +struct snd_soc_dai_driver cg29xx_dai_driver[] = { + { + .name = "cg29xx-codec-dai.0", + .id = 0, + .playback = { + .stream_name = "CG29xx.0 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE, + .formats = CG29XX_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "CG29xx.0 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE, + .formats = CG29XX_SUPPORTED_FMT, + }, + .ops = &cg29xx_dai_driver_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "cg29xx-codec-dai.1", + .id = 1, + .playback = { + .stream_name = "CG29xx.1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE_PCM, + .formats = CG29XX_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "CG29xx.1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE_PCM, + .formats = CG29XX_SUPPORTED_FMT, + }, + .ops = &cg29xx_dai_driver_dai_ops, + .symmetric_rates = 1, + } +}; +EXPORT_SYMBOL_GPL(cg29xx_dai_driver); + +static const char *enum_ifs_input_select[] = { + "BT_SCO", "FM_RX" +}; + +static const char *enum_ifs_output_select[] = { + "BT_SCO", "FM_TX" +}; + +/* If0 Input Select */ +static struct soc_enum if0_input_select = + SOC_ENUM_SINGLE(INTERFACE0_INPUT_SELECT, 0, + ARRAY_SIZE(enum_ifs_input_select), + enum_ifs_input_select); + +/* If1 Input Select */ +static struct soc_enum if1_input_select = + SOC_ENUM_SINGLE(INTERFACE1_INPUT_SELECT, 0, + ARRAY_SIZE(enum_ifs_input_select), + enum_ifs_input_select); + +/* If0 Output Select */ +static struct soc_enum if0_output_select = + SOC_ENUM_SINGLE(INTERFACE0_OUTPUT_SELECT, 0, + ARRAY_SIZE(enum_ifs_output_select), + enum_ifs_output_select); + +/* If1 Output Select */ +static struct soc_enum if1_output_select = + SOC_ENUM_SINGLE(INTERFACE1_OUTPUT_SELECT, 4, + ARRAY_SIZE(enum_ifs_output_select), + enum_ifs_output_select); + +static struct snd_kcontrol_new cg29xx_snd_controls[] = { + SOC_ENUM("If0 Input Select", if0_input_select), + SOC_ENUM("If1 Input Select", if1_input_select), + SOC_ENUM("If0 Output Select", if0_output_select), + SOC_ENUM("If1 Output Select", if1_output_select), +}; + + +static struct cg29xx_codec_dai_data *get_dai_data_codec(struct snd_soc_codec *codec, + int dai_id) +{ + struct cg29xx_codec_dai_data *codec_drvdata = snd_soc_codec_get_drvdata(codec); + return &codec_drvdata[dai_id]; +} + +static struct cg29xx_codec_dai_data *get_dai_data(struct snd_soc_dai *codec_dai) +{ + return get_dai_data_codec(codec_dai->codec, codec_dai->id); +} + +static int cg29xx_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, + unsigned int freq, int dir) +{ + return 0; +} + +static int cg29xx_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai); + unsigned int prot; + unsigned int msel; + prot = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + msel = fmt & SND_SOC_DAIFMT_MASTER_MASK; + + switch (prot) { + case SND_SOC_DAIFMT_I2S: + if (dai_data->config.port != PORT_0_I2S) { + pr_err("cg29xx_dai: unsupported DAI format 0x%x\n", + fmt); + return -EINVAL; + } + + if (msel == SND_SOC_DAIFMT_CBM_CFM) + dai_data->config.conf.i2s.mode = DAI_MODE_MASTER; + else + dai_data->config.conf.i2s.mode = DAI_MODE_SLAVE; + break; + + case SND_SOC_DAIFMT_DSP_B: + if (dai_data->config.port != PORT_1_I2S_PCM || + msel == SND_SOC_DAIFMT_CBM_CFM) { + pr_err("cg29xx_dai: unsupported DAI format 0x%x port=%d,msel=%d\n", + fmt, dai_data->config.port, msel); + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int cg29xx_set_tdm_slot(struct snd_soc_dai *codec_dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, + int slot_width) +{ + struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai); + + if (dai_data->config.port != PORT_1_I2S_PCM) + return -EINVAL; + + dai_data->config.conf.i2s_pcm.slot_0_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT0_SHIFT) ? + true : false; + dai_data->config.conf.i2s_pcm.slot_1_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT1_SHIFT) ? + true : false; + dai_data->config.conf.i2s_pcm.slot_2_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT2_SHIFT) ? + true : false; + dai_data->config.conf.i2s_pcm.slot_3_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT3_SHIFT) ? + true : false; + + dai_data->config.conf.i2s_pcm.slot_0_start = 0; + dai_data->config.conf.i2s_pcm.slot_1_start = slot_width; + dai_data->config.conf.i2s_pcm.slot_2_start = 2 * slot_width; + dai_data->config.conf.i2s_pcm.slot_3_start = 3 * slot_width; + + return 0; +} + +static int cg29xx_configure_endp(struct cg29xx_codec_dai_data *dai_data, + enum cg2900_audio_endpoint_id endpid) +{ + struct cg2900_endpoint_config config; + int err; + enum cg2900_dai_sample_rate dai_sr; + enum cg2900_endpoint_sample_rate endp_sr; + + switch (dai_data->config.port) { + default: + case PORT_0_I2S: + dai_sr = dai_data->config.conf.i2s.sample_rate; + break; + + case PORT_1_I2S_PCM: + dai_sr = dai_data->config.conf.i2s_pcm.sample_rate; + break; + } + + switch (dai_sr) { + default: + case SAMPLE_RATE_8: + endp_sr = ENDPOINT_SAMPLE_RATE_8_KHZ; + break; + case SAMPLE_RATE_16: + endp_sr = ENDPOINT_SAMPLE_RATE_16_KHZ; + break; + case SAMPLE_RATE_44_1: + endp_sr = ENDPOINT_SAMPLE_RATE_44_1_KHZ; + break; + case SAMPLE_RATE_48: + endp_sr = ENDPOINT_SAMPLE_RATE_48_KHZ; + break; + } + + config.endpoint_id = endpid; + + switch (endpid) { + default: + case ENDPOINT_BT_SCO_INOUT: + config.config.sco.sample_rate = endp_sr; + break; + + case ENDPOINT_FM_TX: + case ENDPOINT_FM_RX: + config.config.fm.sample_rate = endp_sr; + break; + } + + err = cg2900_audio_config_endpoint(codec_private.session, &config); + + return err; +} + +static int cg29xx_stop_if(struct cg29xx_codec_dai_data *dai_data, + enum cg29xx_dai_direction direction) +{ + int err = 0; + unsigned int *stream; + + if (direction == CG29XX_DAI_DIRECTION_TX) + stream = &dai_data->tx_active; + else + stream = &dai_data->rx_active; + + if (*stream) { + err = cg2900_audio_stop_stream( + codec_private.session, + *stream); + if (!err) { + *stream = 0; + } else { + pr_err("asoc cg29xx - %s - Failed to stop stream on interface %d.\n", + __func__, + dai_data->config.port); + } + } + + return err; +} + +static int cg29xx_start_if(struct cg29xx_codec_dai_data *dai_data, + enum cg29xx_dai_direction direction) +{ + enum cg2900_audio_endpoint_id if_endpid; + enum cg2900_audio_endpoint_id endpid; + unsigned int *stream; + int err; + + if (dai_data->config.port == PORT_0_I2S) + if_endpid = ENDPOINT_PORT_0_I2S; + else + if_endpid = ENDPOINT_PORT_1_I2S_PCM; + + if (direction == CG29XX_DAI_DIRECTION_RX) { + switch (dai_data->output_select) { + default: + case 0: + endpid = ENDPOINT_BT_SCO_INOUT; + break; + case 1: + endpid = ENDPOINT_FM_TX; + } + stream = &dai_data->rx_active; + } else { + switch (dai_data->input_select) { + default: + case 0: + endpid = ENDPOINT_BT_SCO_INOUT; + break; + case 1: + endpid = ENDPOINT_FM_RX; + } + + stream = &dai_data->tx_active; + } + + if (*stream || (endpid == ENDPOINT_BT_SCO_INOUT)) { + pr_debug("asoc cg29xx - %s - The interface has already been started.\n", + __func__); + return 0; + } + + pr_debug("asoc cg29xx - %s - direction: %d, if_id: %d endpid: %d\n", + __func__, + direction, + if_endpid, + endpid); + + err = cg29xx_configure_endp(dai_data, endpid); + + if (err) { + pr_err("asoc cg29xx - %s - Configure endpoint id: %d failed.\n", + __func__, + endpid); + + return err; + } + + err = cg2900_audio_start_stream(codec_private.session, + if_endpid, + endpid, + stream); + + return err; +} + +static int cg29xx_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int err = 0; + + if (!codec_private.session) + err = cg2900_audio_open(&codec_private.session, NULL); + + return err; +} + +static int cg29xx_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai); + int err = 0; + enum cg29xx_dai_direction direction; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + direction = CG29XX_DAI_DIRECTION_RX; + else + direction = CG29XX_DAI_DIRECTION_TX; + + err = cg29xx_start_if(dai_data, direction); + + return err; +} + +static void cg29xx_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai); + enum cg29xx_dai_direction direction; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + direction = CG29XX_DAI_DIRECTION_RX; + else + direction = CG29XX_DAI_DIRECTION_TX; + + (void) cg29xx_stop_if(dai_data, direction); +} + +static int cg29xx_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *codec_dai) +{ + struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai); + enum cg2900_dai_fs_duration duration = SYNC_DURATION_32; + enum cg2900_dai_bit_clk bclk = BIT_CLK_512; + int sr; + int err = 0; + enum cg2900_dai_stream_ratio ratio = STREAM_RATIO_FM48_VOICE16; + + pr_debug("cg29xx asoc - %s called. Port: %d.\n", + __func__, + dai_data->config.port); + + switch (params_rate(hw_params)) { + case 8000: + sr = SAMPLE_RATE_8; + bclk = BIT_CLK_512; + duration = SYNC_DURATION_32; + ratio = STREAM_RATIO_FM48_VOICE8; + break; + case 16000: + sr = SAMPLE_RATE_16; + bclk = BIT_CLK_512; + duration = SYNC_DURATION_32; + ratio = STREAM_RATIO_FM48_VOICE16; + break; + case 44100: + sr = SAMPLE_RATE_44_1; + break; + case 48000: + sr = SAMPLE_RATE_48; + break; + default: + return -EINVAL; + } + + if (dai_data->config.port == PORT_0_I2S) { + dai_data->config.conf.i2s.sample_rate = sr; + } else { + dai_data->config.conf.i2s_pcm.sample_rate = sr; + dai_data->config.conf.i2s_pcm.duration = duration; + dai_data->config.conf.i2s_pcm.clk = bclk; + dai_data->config.conf.i2s_pcm.ratio = ratio; + } + + if (!(dai_data->tx_active | dai_data->rx_active) && dai_data->config.port != PORT_1_I2S_PCM) { + err = cg2900_audio_set_dai_config( + codec_private.session, + &dai_data->config); + + pr_debug("asoc cg29xx: cg2900_audio_set_dai_config" + "on port %d completed with result: %d.\n", + dai_data->config.port, + err); + } + + return err; +} + +static unsigned int cg29xx_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct cg29xx_codec_dai_data *dai_data; + + switch (reg) { + case INTERFACE0_INPUT_SELECT: + dai_data = get_dai_data_codec(codec, 0); + return dai_data->input_select; + + case INTERFACE1_INPUT_SELECT: + dai_data = get_dai_data_codec(codec, 1); + return dai_data->input_select; + + case INTERFACE0_OUTPUT_SELECT: + dai_data = get_dai_data_codec(codec, 0); + return dai_data->output_select; + + case INTERFACE1_OUTPUT_SELECT: + dai_data = get_dai_data_codec(codec, 1); + return dai_data->output_select; + + default: + return 0; + } + + return 0; +} + +static int cg29xx_codec_write(struct snd_soc_codec *codec, + unsigned int reg, + unsigned int value) +{ + struct cg29xx_codec_dai_data *dai_data; + enum cg29xx_dai_direction direction; + bool restart_if = false; + int old_value; + + switch (reg) { + case INTERFACE0_INPUT_SELECT: + dai_data = get_dai_data_codec(codec, 0); + direction = CG29XX_DAI_DIRECTION_TX; + + old_value = dai_data->input_select; + dai_data->input_select = value; + + if ((old_value ^ value) && dai_data->tx_active) + restart_if = true; + break; + + case INTERFACE1_INPUT_SELECT: + dai_data = get_dai_data_codec(codec, 1); + direction = CG29XX_DAI_DIRECTION_TX; + + old_value = dai_data->input_select; + dai_data->input_select = value; + + if ((old_value ^ value) && dai_data->tx_active) + restart_if = true; + break; + + case INTERFACE0_OUTPUT_SELECT: + dai_data = get_dai_data_codec(codec, 0); + direction = CG29XX_DAI_DIRECTION_RX; + + old_value = dai_data->output_select; + dai_data->output_select = value; + + if ((old_value ^ value) && dai_data->rx_active) + restart_if = true; + break; + + case INTERFACE1_OUTPUT_SELECT: + dai_data = get_dai_data_codec(codec, 1); + direction = CG29XX_DAI_DIRECTION_RX; + + old_value = dai_data->output_select; + dai_data->output_select = value; + + if ((old_value ^ value) && dai_data->rx_active) + restart_if = true; + break; + + default: + return -EINVAL; + } + + if (restart_if) { + (void) cg29xx_stop_if(dai_data, direction); + (void) cg29xx_start_if(dai_data, direction); + } + + return 0; +} + +static int cg29xx_codec_probe(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + snd_soc_add_controls( + codec, + cg29xx_snd_controls, + ARRAY_SIZE(cg29xx_snd_controls)); + + return 0; +} + +static int cg29xx_codec_remove(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +static int cg29xx_codec_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +static int cg29xx_codec_resume(struct snd_soc_codec *codec) +{ + pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name); + + return 0; +} + +struct snd_soc_codec_driver cg29xx_codec_driver = { + .probe = cg29xx_codec_probe, + .remove = cg29xx_codec_remove, + .suspend = cg29xx_codec_suspend, + .resume = cg29xx_codec_resume, + .read = cg29xx_codec_read, + .write = cg29xx_codec_write, +}; + +static int __devinit cg29xx_codec_driver_probe(struct platform_device *pdev) +{ + int ret; + struct cg29xx_codec_dai_data *dai_data; + + pr_debug("%s: Enter.\n", __func__); + + pr_info("%s: Init codec private data..\n", __func__); + dai_data = kzalloc(CG29XX_NBR_OF_DAI * sizeof(struct cg29xx_codec_dai_data), + GFP_KERNEL); + if (dai_data == NULL) + return -ENOMEM; + + dai_data[0].tx_active = 0; + dai_data[0].rx_active = 0; + dai_data[0].input_select = 1; + dai_data[0].output_select = 1; + dai_data[0].config.port = PORT_0_I2S; + dai_data[0].config.conf.i2s.mode = DAI_MODE_SLAVE; + dai_data[0].config.conf.i2s.half_period = HALF_PER_DUR_16; + dai_data[0].config.conf.i2s.channel_sel = CHANNEL_SELECTION_BOTH; + dai_data[0].config.conf.i2s.sample_rate = SAMPLE_RATE_48; + dai_data[0].config.conf.i2s.word_width = WORD_WIDTH_32; + dai_data[1].tx_active = 0; + dai_data[1].rx_active = 0; + dai_data[1].input_select = 0; + dai_data[1].output_select = 0; + dai_data[1].config.port = PORT_1_I2S_PCM; + dai_data[1].config.conf.i2s_pcm.mode = DAI_MODE_SLAVE; + dai_data[1].config.conf.i2s_pcm.slot_0_dir = DAI_DIR_B_RX_A_TX; + dai_data[1].config.conf.i2s_pcm.slot_1_dir = DAI_DIR_B_TX_A_RX; + dai_data[1].config.conf.i2s_pcm.slot_2_dir = DAI_DIR_B_RX_A_TX; + dai_data[1].config.conf.i2s_pcm.slot_3_dir = DAI_DIR_B_RX_A_TX; + dai_data[1].config.conf.i2s_pcm.slot_0_used = true; + dai_data[1].config.conf.i2s_pcm.slot_1_used = false; + dai_data[1].config.conf.i2s_pcm.slot_2_used = false; + dai_data[1].config.conf.i2s_pcm.slot_3_used = false; + dai_data[1].config.conf.i2s_pcm.slot_0_start = 0; + dai_data[1].config.conf.i2s_pcm.slot_1_start = 16; + dai_data[1].config.conf.i2s_pcm.slot_2_start = 32; + dai_data[1].config.conf.i2s_pcm.slot_3_start = 48; + dai_data[1].config.conf.i2s_pcm.protocol = PORT_PROTOCOL_PCM; + dai_data[1].config.conf.i2s_pcm.ratio = STREAM_RATIO_FM48_VOICE16; + dai_data[1].config.conf.i2s_pcm.duration = SYNC_DURATION_32; + dai_data[1].config.conf.i2s_pcm.clk = BIT_CLK_512; + dai_data[1].config.conf.i2s_pcm.sample_rate = SAMPLE_RATE_16; + + platform_set_drvdata(pdev, dai_data); + + pr_info("%s: Register codec.\n", __func__); + ret = snd_soc_register_codec(&pdev->dev, &cg29xx_codec_driver, &cg29xx_dai_driver[0], 2); + if (ret < 0) { + pr_debug("%s: Error: Failed to register codec (ret = %d).\n", + __func__, + ret); + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + return ret; + } + + return 0; +} + +static int __devexit cg29xx_codec_driver_remove(struct platform_device *pdev) +{ + (void)cg2900_audio_close(&codec_private.session); + + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + + return 0; +} + +static int cg29xx_codec_driver_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int cg29xx_codec_driver_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver cg29xx_codec_platform_driver = { + .driver = { + .name = "cg29xx-codec", + .owner = THIS_MODULE, + }, + .probe = cg29xx_codec_driver_probe, + .remove = __devexit_p(cg29xx_codec_driver_remove), + .suspend = cg29xx_codec_driver_suspend, + .resume = cg29xx_codec_driver_resume, +}; + + +static int __devinit cg29xx_codec_platform_driver_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + ret = platform_driver_register(&cg29xx_codec_platform_driver); + if (ret != 0) + pr_err("Failed to register CG29xx platform driver (%d)!\n", ret); + + return ret; +} + +static void __exit cg29xx_codec_platform_driver_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + platform_driver_unregister(&cg29xx_codec_platform_driver); +} + + +module_init(cg29xx_codec_platform_driver_init); +module_exit(cg29xx_codec_platform_driver_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/cg29xx.h b/sound/soc/codecs/cg29xx.h new file mode 100644 index 00000000000..fec52d7cdd7 --- /dev/null +++ b/sound/soc/codecs/cg29xx.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: 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. + */ +#ifndef CG29XX_CODEC_H +#define CG29XX_CODEC_H + +#include <../../../drivers/staging/cg2900/include/cg2900_audio.h> + +struct cg29xx_codec_dai_data { + struct mutex mutex; + unsigned int rx_active; + unsigned int tx_active; + int input_select; + int output_select; + struct cg2900_dai_config config; +}; + +struct cg29xx_codec{ + unsigned int session; +}; + +#define CG29XX_DAI_SLOT0_SHIFT 0 +#define CG29XX_DAI_SLOT1_SHIFT 1 +#define CG29XX_DAI_SLOT2_SHIFT 2 +#define CG29XX_DAI_SLOT3_SHIFT 3 + +#define INTERFACE0_INPUT_SELECT 0x00 +#define INTERFACE1_INPUT_SELECT 0x01 +#define INTERFACE0_OUTPUT_SELECT 0x02 +#define INTERFACE1_OUTPUT_SELECT 0x03 + +#endif /* CG29XX_CODEC_H */ diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig new file mode 100644 index 00000000000..f412c89d8df --- /dev/null +++ b/sound/soc/ux500/Kconfig @@ -0,0 +1,67 @@ +# +# Ux500 SoC audio configuration +# + +menuconfig SND_SOC_UX500 + bool "SoC Audio support for Ux500 platform" + depends on SND_SOC && STM_MSP_SPI + default n + help + Say Y if you want to add support for the Ux500 platform. + +choice + prompt "Platform 5500/8500" + depends on SND_SOC_UX500 + default SND_SOC_U8500 + config SND_SOC_U8500 + bool "Platform - U8500" + config SND_SOC_U5500 + bool "Platform - U5500" +endchoice + +config SND_SOC_UX500_AB3550 + bool "Codec - AB3550" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB3550_CORE + select SND_SOC_AB3550 + default n + help + Say Y if you want to include the AB3550 codec. + +config SND_SOC_UX500_AB5500 + bool "Codec - AB5500" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB5500_CORE + select SND_SOC_AB5500 + default n + help + Say Y if you want to include the AB5500 codec. + +config SND_SOC_UX500_AB8500 + bool "Codec - AB8500" + depends on SND_SOC_UX500 && UX500_SOC_DB8500 && AB8500_CORE && AB8500_GPADC + select SND_SOC_AB8500 + default n + help + Say Y if you want to include AB8500 audio codec. + +config SND_SOC_UX500_CG29XX + bool "Codec - CG29xx" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && CG2900_AUDIO + select SND_SOC_CG29XX + default n + help + Say Y if you want to include CG29xx codec (Combo chip). + +config SND_SOC_UX500_AV8100 + bool "Codec - AV8100" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AV8100 + select SND_SOC_AV8100 + default n + help + Say Y if you want to include AV8100 codec (HDMI chip). + +config SND_SOC_UX500_DEBUG + bool "Activate Ux500 platform debug-mode (pr_debug)" + depends on SND_SOC_UX500 + default n + help + Say Y if you want to add debug level prints for Ux500 code-files. diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile new file mode 100644 index 00000000000..262e44a2812 --- /dev/null +++ b/sound/soc/ux500/Makefile @@ -0,0 +1,46 @@ +# Ux500 Platform Support + +ifdef CONFIG_SND_SOC_UX500_DEBUG +CFLAGS_u8500.o := -DDEBUG +CFLAGS_ux500_pcm.o := -DDEBUG +CFLAGS_ux500_msp_dai.o := -DDEBUG +CFLAGS_ux500_ab3550.o := -DDEBUG +CFLAGS_ux500_ab8500.o := -DDEBUG +CFLAGS_ux500_av8100.o := -DDEBUG +CFLAGS_ux500_cg29xx.o := -DDEBUG +CFLAGS_ux500_msp_i2s.o := -DDEBUG +endif + +ifdef CONFIG_UX500_SOC_DBX500 +snd-soc-ux500-platform-objs := ux500_pcm.o ux500_msp_dai.o ux500_msp_i2s.o +obj-y += snd-soc-ux500-platform.o +endif + +ifdef CONFIG_SND_SOC_UX500_AB8500 +snd-soc-ux500-machine-objs += ux500_ab8500.o +endif + +ifdef CONFIG_SND_SOC_UX500_AV8100 +snd-soc-ux500-machine-objs += ux500_av8100.o +endif + +ifdef CONFIG_SND_SOC_UX500_CG29XX +snd-soc-ux500-machine-objs += ux500_cg29xx.o +endif + +ifdef CONFIG_SND_SOC_UX500_AB5500 +snd-soc-ux500-machine-objs += ux500_ab5500.o +endif + +obj-y += snd-soc-ux500-machine.o + +ifdef CONFIG_UX500_SOC_DB8500 +snd-soc-u8500-objs := u8500.o +obj-y += snd-soc-u8500.o +endif + +ifdef CONFIG_UX500_SOC_DB5500 +snd-soc-u5500-objs := u5500.o +obj-y += snd-soc-u5500.o +endif + diff --git a/sound/soc/ux500/u5500.c b/sound/soc/ux500/u5500.c new file mode 100755 index 00000000000..6787daa9de5 --- /dev/null +++ b/sound/soc/ux500/u5500.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Xie Xiaolei (xie.xiaolei@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/io.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" + +#include <linux/spi/spi.h> +#include <sound/initval.h> + +#ifdef CONFIG_SND_SOC_UX500_AB5500 +#include "ux500_ab5500.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +#include "ux500_av8100.h" +#endif +#ifdef CONFIG_SND_SOC_UX500_CG29XX +#include "ux500_cg29xx.h" +#endif +static struct platform_device *u5500_platform_dev; +/* Create dummy devices for platform drivers */ + +static struct platform_device ux500_pcm = { + .name = "ux500-pcm", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +static struct platform_device av8100_codec = { + .name = "av8100-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +static struct platform_device cg29xx_codec = { + .name = "cg29xx-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif +/* Define the whole U5500 soundcard, linking platform to the codec-drivers */ +struct snd_soc_dai_link u5500_dai_links[] = { + { + .name = "ab5500_0", + .stream_name = "ab5500_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "ab5500-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab5500-codec.0", + .init = ux500_ab5500_machine_codec_init, + .ops = (struct snd_soc_ops[]) { + { + .startup = ux500_ab5500_startup, + .shutdown = ux500_ab5500_shutdown, + .hw_params = ux500_ab5500_hw_params, + } + } + }, + #ifdef CONFIG_SND_SOC_UX500_CG29XX + { + .name = "cg29xx_0", + .stream_name = "cg29xx_0", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "cg29xx-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = u5500_cg29xx_ops, + }, + { + .name = "cg29xx_1", + .stream_name = "cg29xx_1", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "cg29xx-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = u5500_cg29xx_ops, + }, + #endif + { + .name = "ab5500_1", + .stream_name = "ab5500_1", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab5500-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "ab5500-codec.0", + .init = ux500_ab5500_machine_codec_init, + .ops = (struct snd_soc_ops[]) { + { + .startup = ux500_ab5500_startup, + .shutdown = ux500_ab5500_shutdown, + .hw_params = ux500_ab5500_hw_params, + } + } + }, + #ifdef CONFIG_SND_SOC_UX500_AV8100 + { + .name = "hdmi", + .stream_name = "hdmi", + .cpu_dai_name = "ux500-msp-i2s.2", + .codec_dai_name = "av8100-codec-dai", + .platform_name = "ux500-pcm.0", + .codec_name = "av8100-codec.0", + .init = NULL, + .ops = ux500_av8100_ops, + }, + #endif +}; + +static struct snd_soc_card u5500_drvdata = { + .name = "U5500-card", + .probe = NULL, + .dai_link = u5500_dai_links, + .num_links = ARRAY_SIZE(u5500_dai_links), +}; + +static int __init u5500_soc_init(void) +{ + int ret = 0; + + pr_debug("%s: Enter.\n", __func__); + + if (!machine_is_u5500()) + return 0; + + pr_debug("%s: Enter.\n", __func__); + + #ifdef CONFIG_SND_SOC_UX500_AV8100 + pr_debug("%s: Register device to generate a probe for AV8100 codec.\n", + __func__); + platform_device_register(&av8100_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_CG29XX + pr_debug("%s: Register device to generate a probe for CG29xx codec.\n", + __func__); + platform_device_register(&cg29xx_codec); + #endif + pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n", + __func__); + platform_device_register(&ux500_pcm); + + u5500_platform_dev = platform_device_alloc("soc-audio", -1); + if (!u5500_platform_dev) + return -ENOMEM; + + platform_set_drvdata(u5500_platform_dev, &u5500_drvdata); + u5500_drvdata.dev = &u5500_platform_dev->dev; + + ret = platform_device_add(u5500_platform_dev); + if (ret) { + pr_err("%s: Error: Failed to add platform device (%s).\n", + __func__, + u5500_drvdata.name); + platform_device_put(u5500_platform_dev); + } + + return ret; +} + +static void __exit u5500_soc_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + platform_device_unregister(u5500_platform_dev); +} + +module_init(u5500_soc_init); +module_exit(u5500_soc_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c new file mode 100644 index 00000000000..516008fcc6a --- /dev/null +++ b/sound/soc/ux500/u8500.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja (ola.o.lilja@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/io.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" + +#include <linux/spi/spi.h> +#include <sound/initval.h> + +#ifdef CONFIG_SND_SOC_UX500_AB3550 +#include "ux500_ab3550.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_AB8500 +#include <sound/ux500_ab8500.h> +#endif + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +#include "ux500_av8100.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +#include "ux500_cg29xx.h" +#endif + + +static struct platform_device *u8500_platform_dev; + +/* Create dummy devices for platform drivers */ + +static struct platform_device ux500_pcm = { + .name = "ux500-pcm", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +static struct platform_device av8100_codec = { + .name = "av8100-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +static struct platform_device cg29xx_codec = { + .name = "cg29xx-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +/* Define the whole U8500 soundcard, linking platform to the codec-drivers */ +struct snd_soc_dai_link u8500_dai_links[] = { + #ifdef CONFIG_SND_SOC_UX500_AV8100 + { + .name = "hdmi", + .stream_name = "hdmi", + .cpu_dai_name = "ux500-msp-i2s.2", + .codec_dai_name = "av8100-codec-dai", + .platform_name = "ux500-pcm.0", + .codec_name = "av8100-codec.0", + .init = NULL, + .ops = ux500_av8100_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_AB3550 + { + .name = "ab3550_0", + .stream_name = "ab3550_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "ab3550-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab3550-codec.11", + .init = NULL, + .ops = ux500_ab3550_ops, + }, + { + .name = "ab3550_1", + .stream_name = "ab3550_1", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab3550-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "ab3550-codec.11", + .init = NULL, + .ops = ux500_ab3550_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_AB8500 + { + .name = "ab8500_0", + .stream_name = "ab8500_0", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab8500-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab8500-codec.0", + .init = ux500_ab8500_machine_codec_init, + .ops = ux500_ab8500_ops, + }, + { + .name = "ab8500_1", + .stream_name = "ab8500_1", + .cpu_dai_name = "ux500-msp-i2s.3", + .codec_dai_name = "ab8500-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "ab8500-codec.0", + .init = NULL, + .ops = ux500_ab8500_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_CG29XX + { + .name = "cg29xx_0", + .stream_name = "cg29xx_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "cg29xx-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = ux500_cg29xx_ops, + }, + #endif +}; + +static struct snd_soc_card u8500_drvdata = { + .name = "U8500-card", + .probe = NULL, + .dai_link = u8500_dai_links, + .num_links = ARRAY_SIZE(u8500_dai_links), +}; + +static int __init u8500_soc_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + if (machine_is_u5500()) + return 0; + + #ifdef CONFIG_SND_SOC_UX500_AV8100 + pr_debug("%s: Register device to generate a probe for AV8100 codec.\n", + __func__); + platform_device_register(&av8100_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_CG29XX + pr_debug("%s: Register device to generate a probe for CG29xx codec.\n", + __func__); + platform_device_register(&cg29xx_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_AB8500 + pr_debug("%s: Calling init-function for AB8500 machine driver.\n", + __func__); + ret = ux500_ab8500_soc_machine_drv_init(); + if (ret) + pr_err("%s: ux500_ab8500_soc_machine_drv_init failed (%d).\n", + __func__, ret); + #endif + + pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n", + __func__); + platform_device_register(&ux500_pcm); + + pr_debug("%s: Allocate platform device 'soc-audio'.\n", + __func__); + u8500_platform_dev = platform_device_alloc("soc-audio", -1); + if (!u8500_platform_dev) + return -ENOMEM; + + pr_debug("%s: Card %s: num_links = %d\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.num_links); + pr_debug("%s: Card %s: DAI-link 0: name = %s\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.dai_link[0].name); + pr_debug("%s: Card %s: DAI-link 0: stream_name = %s\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.dai_link[0].stream_name); + + pr_debug("%s: Card %s: Set platform drvdata.\n", + __func__, + u8500_drvdata.name); + platform_set_drvdata(u8500_platform_dev, &u8500_drvdata); + u8500_drvdata.dev = &u8500_platform_dev->dev; + + pr_debug("%s: Card %s: Add platform device.\n", + __func__, + u8500_drvdata.name); + ret = platform_device_add(u8500_platform_dev); + if (ret) { + pr_err("%s: Error: Failed to add platform device (%s).\n", + __func__, + u8500_drvdata.name); + platform_device_put(u8500_platform_dev); + } + + return ret; +} + +static void __exit u8500_soc_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + #ifdef CONFIG_SND_SOC_UX500_AB8500 + pr_debug("%s: Calling exit-function for AB8500 machine driver.\n", + __func__); + ux500_ab8500_soc_machine_drv_cleanup(); + #endif + + pr_debug("%s: Unregister platform device (%s).\n", + __func__, + u8500_drvdata.name); + platform_device_unregister(u8500_platform_dev); +} + +module_init(u8500_soc_init); +module_exit(u8500_soc_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_ab3550.c b/sound/soc/ux500/ux500_ab3550.c new file mode 100644 index 00000000000..7e144c0e4d2 --- /dev/null +++ b/sound/soc/ux500/ux500_ab3550.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: 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 <sound/soc.h> +#include "../codecs/ab3550.h" + +static int ux500_ab3550_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static void ux500_ab3550_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s: Enter.\n", __func__); +} + +static int ux500_ab3550_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + int channels = params_channels(params); + + pr_debug("%s: Enter.\n", __func__); + pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name); + pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id); + pr_debug("%s: substream->name = %s.\n", __func__, substream->name); + pr_debug("%s: substream->number = %d.\n", __func__, substream->number); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n", + __func__, + ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n", + __func__, + ret); + return ret; + } + + return ret; +} + +struct snd_soc_ops ux500_ab3550_ops[] = { + { + .startup = ux500_ab3550_startup, + .shutdown = ux500_ab3550_shutdown, + .hw_params = ux500_ab3550_hw_params, + } +}; diff --git a/sound/soc/ux500/ux500_ab3550.h b/sound/soc/ux500/ux500_ab3550.h new file mode 100644 index 00000000000..53ea3902d36 --- /dev/null +++ b/sound/soc/ux500/ux500_ab3550.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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. + */ + +#ifndef UX500_AB3550_H +#define UX500_AB3550_H + +extern struct snd_soc_ops ux500_ab3550_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_ab5500.c b/sound/soc/ux500/ux500_ab5500.c new file mode 100755 index 00000000000..3a1dab0a990 --- /dev/null +++ b/sound/soc/ux500/ux500_ab5500.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: 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 <sound/soc.h> +#include <linux/clk.h> +#include "../codecs/ab5500.h" +#include "ux500_msp_dai.h" + +/* For a workwround purpose we enable sysclk + by default this will be changed later */ +static unsigned int sysclk_state = 1;/* Enabled */ +static struct clk *ux500_ab5500_sysclk; + +static int sysclk_input_select_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int sysclk_input_select_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = sysclk_state; + return 0; +} + +static int sysclk_input_select_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + sysclk_state = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new sysclk_input_select_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sysclk Input Select", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = sysclk_input_select_control_info, + .get = sysclk_input_select_control_get, + .put = sysclk_input_select_control_put +}; + +int ux500_ab5500_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + + if (sysclk_state == 1) { + ret = clk_enable(ux500_ab5500_sysclk); + if (ret) + dev_err(codec->dev, "failed to enable clock %d\n", ret); + } + + return ret; +} + +void ux500_ab5500_shutdown(struct snd_pcm_substream *substream) +{ + pr_info("%s: Enter.\n", __func__); + if (sysclk_state == 1) + clk_disable(ux500_ab5500_sysclk); +} + +int ux500_ab5500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + int channels = params_channels(params); + + printk(KERN_DEBUG "%s: Enter.\n", __func__); + printk(KERN_DEBUG "%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name); + printk(KERN_DEBUG "%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id); + printk(KERN_DEBUG "%s: substream->name = %s.\n", __func__, substream->name); + printk(KERN_DEBUG "%s: substream->number = %d.\n", __func__, substream->number); + printk(KERN_DEBUG "%s: channels = %d.\n", __func__, channels); + printk(KERN_DEBUG "%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + printk(KERN_DEBUG "%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + return ret; +} + +int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + + snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&sysclk_input_select_control, codec)); + + ux500_ab5500_sysclk = clk_get(codec->dev, "sysclk"); + if (IS_ERR(ux500_ab5500_sysclk)) { + dev_err(codec->dev, "could not get sysclk %ld\n", + PTR_ERR(ux500_ab5500_sysclk)); + ret = PTR_ERR(ux500_ab5500_sysclk); + } + + return ret; +} diff --git a/sound/soc/ux500/ux500_ab5500.h b/sound/soc/ux500/ux500_ab5500.h new file mode 100755 index 00000000000..8a9be4b98d0 --- /dev/null +++ b/sound/soc/ux500/ux500_ab5500.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Xie Xiaolei (xie.xiaolei@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. + */ + +#ifndef UX500_AB5500_H +#define UX500_AB5500_H + +struct snd_soc_pcm_runtime; + +int ux500_ab5500_startup(struct snd_pcm_substream *substream); + +void ux500_ab5500_shutdown(struct snd_pcm_substream *substream); + +int ux500_ab5500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *runtime); + +#endif diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c new file mode 100644 index 00000000000..b3819f88b10 --- /dev/null +++ b/sound/soc/ux500/ux500_ab8500.c @@ -0,0 +1,1064 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Mikko J. Lehto <mikko.lehto@symbio.com>, + * Mikko Sarmanne <mikko.sarmanne@symbio.com>, + * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>. + * Ola Lilja <ola.o.lilja@stericsson.com>, + * Kristoffer Karlsson <kristoffer.karlsson@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/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <mach/hardware.h> +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" +#include "../codecs/ab8500_audio.h" + +#define TX_SLOT_MONO 0x0008 +#define TX_SLOT_STEREO 0x000a +#define RX_SLOT_MONO 0x0001 +#define RX_SLOT_STEREO 0x0003 +#define TX_SLOT_8CH 0x00FF +#define RX_SLOT_8CH 0x00FF + +#define DEF_TX_SLOTS TX_SLOT_STEREO +#define DEF_RX_SLOTS RX_SLOT_MONO + +#define DRIVERMODE_NORMAL 0 +#define DRIVERMODE_CODEC_ONLY 1 + +static struct snd_soc_jack jack; +static bool vibra_on; + +/* Power-control */ +static DEFINE_MUTEX(power_lock); +static int ab8500_power_count; + +/* ADCM-control */ +static DEFINE_MUTEX(adcm_lock); +#define GPADC_MIN_DELTA_DELAY 500 +#define GPADC_MAX_DELTA_DELAY 1000 +#define GPADC_MAX_VOLT_DIFF 20 +#define GPADC_MAX_ITERATIONS 4 + +/* Clocks */ +/* audioclk -> intclk -> sysclk/ulpclk */ +static int master_clock_sel; +static struct clk *clk_ptr_audioclk; +static struct clk *clk_ptr_intclk; +static struct clk *clk_ptr_sysclk; +static struct clk *clk_ptr_ulpclk; +static struct clk *clk_ptr_gpio1; + +static const char * const enum_mclk[] = { + "SYSCLK", + "ULPCLK" +}; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); + +/* ANC States */ +static const char * const enum_anc_state[] = { + "Unconfigured", + "Apply FIR and IIR", + "FIR and IIR are configured", + "Apply FIR", + "FIR is configured", + "Apply IIR", + "IIR is configured", + "Error" +}; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state); +enum anc_state { + ANC_UNCONFIGURED = 0, + ANC_APPLY_FIR_IIR = 1, + ANC_FIR_IIR_CONFIGURED = 2, + ANC_APPLY_FIR = 3, + ANC_FIR_CONFIGURED = 4, + ANC_APPLY_IIR = 5, + ANC_IIR_CONFIGURED = 6, + ANC_ERROR = 7 +}; +static enum anc_state anc_status = ANC_UNCONFIGURED; + +/* Regulators */ +enum regulator_idx { + REGULATOR_AUDIO, + REGULATOR_DMIC, + REGULATOR_AMIC1, + REGULATOR_AMIC2 +}; +static struct regulator_bulk_data reg_info[4] = { + { .consumer = NULL, .supply = "v-audio" }, + { .consumer = NULL, .supply = "v-dmic" }, + { .consumer = NULL, .supply = "v-amic1" }, + { .consumer = NULL, .supply = "v-amic2" } +}; +static bool reg_enabled[4] = { + false, + false, + false, + false +}; +static int reg_claim[4]; +enum amic_idx { AMIC_1A, AMIC_1B, AMIC_2 }; +struct amic_conf { + enum regulator_idx reg_id; + bool enabled; + char *name; +}; +static struct amic_conf amic_info[3] = { + { REGULATOR_AMIC1, false, "amic1a" }, + { REGULATOR_AMIC1, false, "amic1b" }, + { REGULATOR_AMIC2, false, "amic2" } +}; +static DEFINE_MUTEX(amic_conf_lock); + +static const char *enum_amic_reg_conf[2] = { "v-amic1", "v-amic2" }; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_amicconf, enum_amic_reg_conf); + +/* Slot configuration */ +static unsigned int tx_slots = DEF_TX_SLOTS; +static unsigned int rx_slots = DEF_RX_SLOTS; + +/* Regulators */ + +static int enable_regulator(enum regulator_idx idx) +{ + int ret; + + if (reg_info[idx].consumer == NULL) { + pr_err("%s: Failure to enable regulator '%s'\n", + __func__, reg_info[idx].supply); + return -EIO; + } + + if (reg_enabled[idx]) + return 0; + + ret = regulator_enable(reg_info[idx].consumer); + if (ret != 0) { + pr_err("%s: Failure to enable regulator '%s' (ret = %d)\n", + __func__, reg_info[idx].supply, ret); + return -EIO; + }; + + reg_enabled[idx] = true; + pr_debug("%s: Enabled regulator '%s', status: %d, %d, %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1], + (int)reg_enabled[2], + (int)reg_enabled[3]); + return 0; +} + +static void disable_regulator(enum regulator_idx idx) +{ + if (reg_info[idx].consumer == NULL) { + pr_err("%s: Failure to disable regulator '%s'\n", + __func__, reg_info[idx].supply); + return; + } + + if (!reg_enabled[idx]) + return; + + regulator_disable(reg_info[idx].consumer); + + reg_enabled[idx] = false; + pr_debug("%s: Disabled regulator '%s', status: %d, %d, %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1], + (int)reg_enabled[2], + (int)reg_enabled[3]); +} + +static int create_regulators(void) +{ + int i, status = 0; + + pr_debug("%s: Enter.\n", __func__); + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) + reg_info[i].consumer = NULL; + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + reg_info[i].consumer = regulator_get(NULL, reg_info[i].supply); + if (IS_ERR(reg_info[i].consumer)) { + status = PTR_ERR(reg_info[i].consumer); + pr_err("%s: ERROR: Failed to get regulator '%s' (ret = %d)!\n", + __func__, reg_info[i].supply, status); + reg_info[i].consumer = NULL; + goto err_get; + } + } + + return 0; + +err_get: + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + if (reg_info[i].consumer) { + regulator_put(reg_info[i].consumer); + reg_info[i].consumer = NULL; + } + } + + return status; +} + +static int claim_amic_regulator(enum amic_idx amic_id) +{ + enum regulator_idx reg_id = amic_info[amic_id].reg_id; + int ret = 0; + + reg_claim[reg_id]++; + if (reg_claim[reg_id] > 1) + goto cleanup; + + ret = enable_regulator(reg_id); + if (ret < 0) { + pr_err("%s: Failed to claim %s for %s (ret = %d)!", + __func__, reg_info[reg_id].supply, + amic_info[amic_id].name, ret); + reg_claim[reg_id]--; + } + +cleanup: + amic_info[amic_id].enabled = (ret == 0); + + return ret; +} + +static void release_amic_regulator(enum amic_idx amic_id) +{ + enum regulator_idx reg_id = amic_info[amic_id].reg_id; + + reg_claim[reg_id]--; + if (reg_claim[reg_id] <= 0) { + disable_regulator(reg_id); + reg_claim[reg_id] = 0; + } + + amic_info[amic_id].enabled = false; +} + +/* Power/clock control */ + +static int ux500_ab8500_power_control_inc(void) +{ + int ret = 0; + + pr_debug("%s: Enter.\n", __func__); + + mutex_lock(&power_lock); + + ab8500_power_count++; + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count-1, + ab8500_power_count); + + if (ab8500_power_count == 1) { + /* Turn on audio-regulator */ + ret = enable_regulator(REGULATOR_AUDIO); + if (ret < 0) + goto out; + + /* Enable audio-clock */ + ret = clk_set_parent(clk_ptr_intclk, + (master_clock_sel == 0) ? clk_ptr_sysclk : clk_ptr_ulpclk); + if (ret != 0) { + pr_err("%s: ERROR: Setting master-clock to %s failed (ret = %d)!", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK", + ret); + ret = -EIO; + goto clk_err; + } + pr_debug("%s: Enabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + ret = clk_enable(clk_ptr_audioclk); + if (ret != 0) { + pr_err("%s: ERROR: clk_enable failed (ret = %d)!", __func__, ret); + ret = -EIO; + ab8500_power_count = 0; + goto clk_err; + } + + /* Power on audio-parts of AB8500 */ + ret = ab8500_audio_power_control(true); + } + + goto out; + +clk_err: + disable_regulator(REGULATOR_AUDIO); + +out: + mutex_unlock(&power_lock); + + pr_debug("%s: Exit.\n", __func__); + + return ret; +} + +static void ux500_ab8500_power_control_dec(void) +{ + pr_debug("%s: Enter.\n", __func__); + + mutex_lock(&power_lock); + + ab8500_power_count--; + + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count+1, + ab8500_power_count); + + if (ab8500_power_count == 0) { + /* Power off audio-parts of AB8500 */ + ab8500_audio_power_control(false); + + /* Disable audio-clock */ + pr_debug("%s: Disabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + clk_disable(clk_ptr_audioclk); + + /* Turn off audio-regulator */ + disable_regulator(REGULATOR_AUDIO); + } + + mutex_unlock(&power_lock); + + pr_debug("%s: Exit.\n", __func__); +} + +/* Controls - Non-DAPM Non-ASoC */ + +static int mclk_input_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = master_clock_sel; + + return 0; +} + +static int mclk_input_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + + val = (ucontrol->value.enumerated.item[0] != 0); + if (master_clock_sel == val) + return 0; + + master_clock_sel = val; + + return 1; +} + +static const struct snd_kcontrol_new mclk_input_control = \ + SOC_ENUM_EXT("Master Clock Select", soc_enum_mclk, + mclk_input_control_get, mclk_input_control_put); + +static int anc_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + mutex_lock(&codec->mutex); + ucontrol->value.integer.value[0] = anc_status; + mutex_unlock(&codec->mutex); + + return 0; +} + +static int anc_status_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec; + bool apply_fir, apply_iir; + int req, ret; + + pr_debug("%s: Enter.\n", __func__); + + req = ucontrol->value.integer.value[0]; + if (req != ANC_APPLY_FIR_IIR && req != ANC_APPLY_FIR && + req != ANC_APPLY_IIR) { + pr_err("%s: ERROR: Unsupported status to set '%s'!\n", + __func__, enum_anc_state[req]); + return -EINVAL; + } + apply_fir = req == ANC_APPLY_FIR || req == ANC_APPLY_FIR_IIR; + apply_iir = req == ANC_APPLY_IIR || req == ANC_APPLY_FIR_IIR; + + codec = snd_kcontrol_chip(kcontrol); + + ret = ux500_ab8500_power_control_inc(); + if (ret < 0) { + pr_err("%s: ERROR: Failed to enable power (ret = %d)!\n", + __func__, ret); + goto cleanup; + } + + mutex_lock(&codec->mutex); + + ab8500_audio_anc_configure(codec, apply_fir, apply_iir); + + if (apply_fir) { + if (anc_status == ANC_IIR_CONFIGURED) + anc_status = ANC_FIR_IIR_CONFIGURED; + else if (anc_status != ANC_FIR_IIR_CONFIGURED) + anc_status = ANC_FIR_CONFIGURED; + } + if (apply_iir) { + if (anc_status == ANC_FIR_CONFIGURED) + anc_status = ANC_FIR_IIR_CONFIGURED; + else if (anc_status != ANC_FIR_IIR_CONFIGURED) + anc_status = ANC_IIR_CONFIGURED; + } + + mutex_unlock(&codec->mutex); + + ux500_ab8500_power_control_dec(); + +cleanup: + if (ret < 0) + pr_err("%s: Unable to configure ANC! (ret = %d)\n", + __func__, ret); + + pr_debug("%s: Exit.\n", __func__); + + return (ret < 0) ? ret : 1; +} + +static const struct snd_kcontrol_new anc_status_control = \ + SOC_ENUM_EXT("ANC Status", soc_enum_ancstate, + anc_status_control_get, anc_status_control_put); + +static int amic_reg_control_get(struct snd_ctl_elem_value *ucontrol, + enum amic_idx amic_id) +{ + ucontrol->value.integer.value[0] = + (amic_info[amic_id].reg_id == REGULATOR_AMIC2); + + return 0; +} + +static int amic_reg_control_put(struct snd_ctl_elem_value *ucontrol, + enum amic_idx amic_id) +{ + enum regulator_idx old_reg_id, new_reg_id; + int ret = 0; + + if (ucontrol->value.integer.value[0] == 0) + new_reg_id = REGULATOR_AMIC1; + else + new_reg_id = REGULATOR_AMIC2; + + mutex_lock(&amic_conf_lock); + + old_reg_id = amic_info[amic_id].reg_id; + if (old_reg_id == new_reg_id) + goto cleanup; + + if (!amic_info[amic_id].enabled) { + amic_info[amic_id].reg_id = new_reg_id; + goto cleanup; + } + + release_amic_regulator(amic_id); + amic_info[amic_id].reg_id = new_reg_id; + ret = claim_amic_regulator(amic_id); + +cleanup: + mutex_unlock(&amic_conf_lock); + + return (ret < 0) ? 0 : 1; +} + +static int amic1a_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_1A); +} + +static int amic1a_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_1A); +} + +static int amic1b_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_1B); +} + +static int amic1b_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_1B); +} + +static int amic2_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_2); +} + +static int amic2_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_2); +} + +static const struct snd_kcontrol_new mic1a_regulator_control = \ + SOC_ENUM_EXT("Mic 1A Regulator", soc_enum_amicconf, + amic1a_reg_control_get, amic1a_reg_control_put); +static const struct snd_kcontrol_new mic1b_regulator_control = \ + SOC_ENUM_EXT("Mic 1B Regulator", soc_enum_amicconf, + amic1b_reg_control_get, amic1b_reg_control_put); +static const struct snd_kcontrol_new mic2_regulator_control = \ + SOC_ENUM_EXT("Mic 2 Regulator", soc_enum_amicconf, + amic2_reg_control_get, amic2_reg_control_put); + +/* DAPM-events */ + +static int dapm_audioreg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + ux500_ab8500_power_control_inc(); + else + ux500_ab8500_power_control_dec(); + + return 0; +} + +static int dapm_amicreg_event(enum amic_idx amic_id, int event) +{ + int ret = 0; + + mutex_lock(&amic_conf_lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = claim_amic_regulator(amic_id); + else if (amic_info[amic_id].enabled) + release_amic_regulator(amic_id); + + mutex_unlock(&amic_conf_lock); + + return ret; +} + +static int dapm_amic1areg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_1A, event); +} + +static int dapm_amic1breg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_1B, event); +} + +static int dapm_amic2reg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_2, event); +} + +static int dapm_dmicreg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = enable_regulator(REGULATOR_DMIC); + else + disable_regulator(REGULATOR_DMIC); + + return ret; +} + +/* DAPM-widgets */ + +static const struct snd_soc_dapm_widget ux500_ab8500_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("AUDIO Regulator", + SND_SOC_NOPM, 0, 0, dapm_audioreg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC1A Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic1areg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC1B Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic1breg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC2 Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic2reg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DMIC Regulator", + SND_SOC_NOPM, 0, 0, dapm_dmicreg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +/* DAPM-routes */ + +static const struct snd_soc_dapm_route ux500_ab8500_dapm_intercon[] = { + + /* Power AB8500 audio-block when AD/DA is active */ + {"DAC", NULL, "AUDIO Regulator"}, + {"ADC", NULL, "AUDIO Regulator"}, + + /* Power AB8500 audio-block when LineIn is active */ + {"LINL Enable", NULL, "AUDIO Regulator"}, + {"LINR Enable", NULL, "AUDIO Regulator"}, + + /* Power configured regulator when an analog mic is enabled */ + {"MIC1A Input", NULL, "AMIC1A Regulator"}, + {"MIC1B Input", NULL, "AMIC1B Regulator"}, + {"MIC2 Input", NULL, "AMIC2 Regulator"}, + + /* Power DMIC-regulator when any digital mic is enabled */ + {"DMic 1", NULL, "DMIC Regulator"}, + {"DMic 2", NULL, "DMIC Regulator"}, + {"DMic 3", NULL, "DMIC Regulator"}, + {"DMic 4", NULL, "DMIC Regulator"}, + {"DMic 5", NULL, "DMIC Regulator"}, + {"DMic 6", NULL, "DMIC Regulator"}, +}; + +static int add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(&codec->dapm, + ux500_ab8500_dapm_widgets, + ARRAY_SIZE(ux500_ab8500_dapm_widgets)); + if (ret < 0) { + pr_err("%s: Failed to create DAPM controls (%d).\n", + __func__, ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&codec->dapm, + ux500_ab8500_dapm_intercon, + ARRAY_SIZE(ux500_ab8500_dapm_intercon)); + if (ret < 0) { + pr_err("%s: Failed to add DAPM routes (%d).\n", + __func__, ret); + return ret; + } + + return 0; +} + +/* ASoC */ + +int ux500_ab8500_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s: Enter\n", __func__); + + /* Enable gpio.1-clock (needed by DSP in burst mode) */ + ret = clk_enable(clk_ptr_gpio1); + if (ret) { + pr_err("%s: ERROR: clk_enable(gpio.1) failed (ret = %d)!", __func__, ret); + return ret; + } + + return 0; +} + +void ux500_ab8500_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s: Enter\n", __func__); + + /* Reset slots configuration to default(s) */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tx_slots = DEF_TX_SLOTS; + else + rx_slots = DEF_RX_SLOTS; + + clk_disable(clk_ptr_gpio1); +} + +int ux500_ab8500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int fmt, fmt_if1; + int channels, ret = 0, slots, slot_width, driver_mode; + bool streamIsPlayback; + + pr_debug("%s: Enter\n", __func__); + + pr_debug("%s: substream->pcm->name = %s\n" + "substream->pcm->id = %s.\n" + "substream->name = %s.\n" + "substream->number = %d.\n", + __func__, + substream->pcm->name, + substream->pcm->id, + substream->name, + substream->number); + + channels = params_channels(params); + + /* Setup codec depending on driver-mode */ + driver_mode = (channels == 8) ? + DRIVERMODE_CODEC_ONLY : DRIVERMODE_NORMAL; + pr_debug("%s: Driver-mode: %s.\n", + __func__, + (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); + + ab8500_audio_set_bit_delay(codec_dai, 1); + + if (driver_mode == DRIVERMODE_NORMAL) { + ab8500_audio_set_word_length(codec_dai, 16); + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + } else { + ab8500_audio_set_word_length(codec_dai, 20); + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_GATED; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + pr_err("%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n", + __func__, + ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) { + pr_err("%s: ERROR: snd_soc_dai_set_fmt for cpu_dai (ret = %d)!\n", + __func__, + ret); + return ret; + } + + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + /* Setup TDM-slots */ + + streamIsPlayback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + switch (channels) { + case 1: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_MONO : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_MONO; + break; + case 2: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_STEREO : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_STEREO; + break; + case 8: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_8CH : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_8CH; + break; + default: + return -EINVAL; + } + + pr_debug("%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", + __func__, tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, slot_width); + if (ret) + return ret; + + pr_debug("%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", + __func__, tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, slot_width); + if (ret) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: Setup IF1 for FM-radio.\n", __func__); + fmt_if1 = SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_I2S; + ret = ab8500_audio_setup_if1(codec_dai->codec, fmt_if1, 16, 1); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_ops ux500_ab8500_ops[] = { + { + .hw_params = ux500_ab8500_hw_params, + .startup = ux500_ab8500_startup, + .shutdown = ux500_ab8500_shutdown, + } +}; + +int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + pr_debug("%s Enter.\n", __func__); + + ret = snd_soc_jack_new(codec, + "AB8500 Hs Status", + SND_JACK_HEADPHONE | + SND_JACK_MICROPHONE | + SND_JACK_HEADSET | + SND_JACK_LINEOUT | + SND_JACK_MECHANICAL | + SND_JACK_VIDEOOUT, + &jack); + if (ret < 0) { + pr_err("%s: ERROR: Failed to create Jack (ret = %d)!\n", __func__, ret); + return ret; + } + + /* Add controls */ + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mclk_input_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &anc_status_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic1a_regulator_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic1b_regulator_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic2_regulator_control, codec)); + + /* Get references to clock-nodes */ + clk_ptr_sysclk = NULL; + clk_ptr_ulpclk = NULL; + clk_ptr_intclk = NULL; + clk_ptr_audioclk = NULL; + clk_ptr_gpio1 = NULL; + clk_ptr_sysclk = clk_get(codec->dev, "sysclk"); + if (IS_ERR(clk_ptr_sysclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_ulpclk = clk_get(codec->dev, "ulpclk"); + if (IS_ERR(clk_ptr_ulpclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_intclk = clk_get(codec->dev, "intclk"); + if (IS_ERR(clk_ptr_intclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_audioclk = clk_get(codec->dev, "audioclk"); + if (IS_ERR(clk_ptr_audioclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_gpio1 = clk_get_sys("gpio.1", NULL); + if (IS_ERR(clk_ptr_gpio1)) { + pr_err("ERROR: clk_get_sys(gpio.1) failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + + /* Set intclk default parent to ulpclk */ + ret = clk_set_parent(clk_ptr_intclk, clk_ptr_ulpclk); + if (ret) { + pr_err("%s: ERROR: Setting intclk parent to ulpclk failed (ret = %d)!", + __func__, + ret); + return -EFAULT; + } + + master_clock_sel = 1; + + ab8500_power_count = 0; + + reg_claim[REGULATOR_AMIC1] = 0; + reg_claim[REGULATOR_AMIC2] = 0; + + /* Add DAPM-widgets */ + ret = add_widgets(codec); + if (ret < 0) { + pr_err("%s: Failed add widgets (%d).\n", __func__, ret); + return ret; + } + + return 0; +} + +int ux500_ab8500_soc_machine_drv_init(void) +{ + int status = 0; + + pr_debug("%s: Enter.\n", __func__); + + status = create_regulators(); + if (status < 0) { + pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n", + __func__, status); + return status; + } + + vibra_on = false; + + return 0; +} + +void ux500_ab8500_soc_machine_drv_cleanup(void) +{ + pr_debug("%s: Enter.\n", __func__); + + regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info); + + if (clk_ptr_sysclk != NULL) + clk_put(clk_ptr_sysclk); + if (clk_ptr_ulpclk != NULL) + clk_put(clk_ptr_ulpclk); + if (clk_ptr_intclk != NULL) + clk_put(clk_ptr_intclk); + if (clk_ptr_audioclk != NULL) + clk_put(clk_ptr_audioclk); + if (clk_ptr_gpio1 != NULL) + clk_put(clk_ptr_gpio1); +} + +/* + * Measures a relative stable voltage from spec. input on spec channel + */ +static int gpadc_convert_stable(struct ab8500_gpadc *gpadc, + u8 channel, int *value) +{ + int i = GPADC_MAX_ITERATIONS; + int mv1, mv2, dmv; + + mv1 = ab8500_gpadc_convert(gpadc, channel); + do { + i--; + usleep_range(GPADC_MIN_DELTA_DELAY, GPADC_MAX_DELTA_DELAY); + mv2 = ab8500_gpadc_convert(gpadc, channel); + dmv = abs(mv2 - mv1); + mv1 = mv2; + } while (i > 0 && dmv > GPADC_MAX_VOLT_DIFF); + + if (mv1 < 0 || dmv > GPADC_MAX_VOLT_DIFF) + return -EIO; + + *value = mv1; + + return 0; +} + +/* Extended interface */ + +void ux500_ab8500_audio_pwm_vibra(unsigned char speed_left_pos, + unsigned char speed_left_neg, + unsigned char speed_right_pos, + unsigned char speed_right_neg) +{ + bool vibra_on_new; + + vibra_on_new = speed_left_pos | speed_left_neg | speed_right_pos | speed_right_neg; + if ((!vibra_on_new) && (vibra_on)) { + pr_debug("%s: PWM-vibra off.\n", __func__); + vibra_on = false; + + ux500_ab8500_power_control_dec(); + } + + if ((vibra_on_new) && (!vibra_on)) { + pr_debug("%s: PWM-vibra on.\n", __func__); + vibra_on = true; + + ux500_ab8500_power_control_inc(); + } + + ab8500_audio_pwm_vibra(speed_left_pos, + speed_left_neg, + speed_right_pos, + speed_right_neg); +} + +int ux500_ab8500_audio_gpadc_measure(struct ab8500_gpadc *gpadc, + u8 channel, bool mode, int *value) +{ + int ret = 0; + int adcm = (mode) ? + AB8500_AUDIO_ADCM_FORCE_UP : + AB8500_AUDIO_ADCM_FORCE_DOWN; + + pr_debug("%s: Enter.\n", __func__); + + mutex_lock(&adcm_lock); + + ret = ux500_ab8500_power_control_inc(); + if (ret < 0) { + pr_err("%s: ERROR: Failed to enable power (ret = %d)!\n", + __func__, ret); + goto power_failure; + } + + ret = ab8500_audio_set_adcm(adcm); + if (ret < 0) { + pr_err("%s: ERROR: Failed to force adcm %s (ret = %d)!\n", + __func__, (mode) ? "UP" : "DOWN", ret); + goto adcm_failure; + } + + ret = gpadc_convert_stable(gpadc, channel, value); + ret |= ab8500_audio_set_adcm(AB8500_AUDIO_ADCM_NORMAL); + +adcm_failure: + ux500_ab8500_power_control_dec(); + +power_failure: + mutex_unlock(&adcm_lock); + + pr_debug("%s: Exit.\n", __func__); + + return ret; +} + +void ux500_ab8500_jack_report(int value) +{ + if (jack.jack) + snd_soc_jack_report(&jack, value, 0xFF); +} +EXPORT_SYMBOL_GPL(ux500_ab8500_jack_report); + diff --git a/sound/soc/ux500/ux500_av8100.c b/sound/soc/ux500/ux500_av8100.c new file mode 100644 index 00000000000..4ccc7531f4b --- /dev/null +++ b/sound/soc/ux500/ux500_av8100.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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 <sound/soc.h> +#include "../codecs/av8100_audio.h" +#include "ux500_av8100.h" +#include "ux500_msp_dai.h" + +static const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +static int ux500_av8100_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int channels = params_channels(params); + unsigned int tx_mask, fmt; + enum hdmi_channel_allocation hdmi_ca; + enum hdmi_audio_channel_count hdmi_cc; + struct hdmi_audio_settings as; + int ret; + + pr_debug("%s: Enter (%s).\n", __func__, stream_str(substream)); + pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name); + pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id); + pr_debug("%s: substream->name = %s.\n", __func__, substream->name); + pr_debug("%s: substream->number = %d.\n", __func__, substream->number); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + switch (channels) { + case 1: + hdmi_cc = AV8100_CODEC_CC_2CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo-setup */ + tx_mask = AV8100_CODEC_MASK_MONO; + break; + case 2: + hdmi_cc = AV8100_CODEC_CC_2CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo */ + tx_mask = AV8100_CODEC_MASK_STEREO; + break; + case 3: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_2DOT1; + break; + case 4: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_QUAD; + break; + case 5: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_5DOT0; + break; + case 6: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1 */ + tx_mask = AV8100_CODEC_MASK_5DOT1; + break; + case 7: + hdmi_cc = AV8100_CODEC_CC_8CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */ + tx_mask = AV8100_CODEC_MASK_7DOT0; + break; + case 8: + hdmi_cc = AV8100_CODEC_CC_8CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */ + tx_mask = AV8100_CODEC_MASK_7DOT1; + break; + default: + pr_err("%s: Unsupported number of channels (channels = %d)!\n", + __func__, + channels); + return -EINVAL; + } + + /* Change HDMI audio-settings for codec-DAI. */ + pr_debug("%s: Change HDMI audio-settings for codec-DAI.\n", __func__); + as.audio_channel_count = hdmi_cc; + as.sampling_frequency = AV8100_CODEC_SF_REFER; + as.sample_size = AV8100_CODEC_SS_REFER; + as.channel_allocation = hdmi_ca; + as.level_shift_value = AV8100_CODEC_LSV_0DB; + as.downmix_inhibit = false; + ret = av8100_audio_change_hdmi_audio_settings(codec_dai, &as); + if (ret < 0) { + pr_err("%s: Unable to change HDMI audio-settings for codec-DAI " + "(av8100_codec_change_hdmi_audio_settings returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set format for codec-DAI */ + fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM; + pr_debug("%s: Setting format for codec-DAI (fmt = %d).\n", + __func__, + fmt); + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + pr_err("%s: Unable to set format for codec-DAI " + "(snd_soc_dai_set_tdm_slot returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set TDM-slot for CPU-DAI */ + pr_debug("%s: Setting TDM-slot for codec-DAI (tx_mask = %d).\n", + __func__, + tx_mask); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_mask, 0, 16, 16); + if (ret < 0) { + pr_err("%s: Unable to set TDM-slot for codec-DAI " + "(snd_soc_dai_set_tdm_slot returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set format for CPU-DAI */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_IF; + pr_debug("%s: Setting DAI-format for Ux500-platform (fmt = %d).\n", + __func__, + fmt); + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) { + pr_err("%s: Unable to set DAI-format for Ux500-platform " + "(snd_soc_dai_set_fmt returned %d).\n", + __func__, + ret); + return ret; + } + + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + return ret; +} + +struct snd_soc_ops ux500_av8100_ops[] = { + { + .hw_params = ux500_av8100_hw_params, + } +}; + diff --git a/sound/soc/ux500/ux500_av8100.h b/sound/soc/ux500/ux500_av8100.h new file mode 100644 index 00000000000..b107b2e1be7 --- /dev/null +++ b/sound/soc/ux500/ux500_av8100.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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. + */ + +#ifndef UX500_AV8100_H +#define UX500_AV8100_H + +extern struct snd_soc_ops ux500_av8100_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_cg29xx.c b/sound/soc/ux500/ux500_cg29xx.c new file mode 100644 index 00000000000..456262c6c0b --- /dev/null +++ b/sound/soc/ux500/ux500_cg29xx.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: 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 <sound/soc.h> +#include "../codecs/cg29xx.h" +#include "ux500_msp_dai.h" + +#define UX500_CG29XX_MSP_CLOCK_FREQ 18900000 +#define U5500_CG29XX_MSP_CLOCK_FREQ 13000000 +#define UX500_CG29XX_DAI_SLOT_WIDTH 16 +#define UX500_CG29XX_DAI_SLOTS 2 +#define UX500_CG29XX_DAI_ACTIVE_SLOTS 0x02 + +int ux500_cg29xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int channels = params_channels(params); + int err; + + pr_debug("%s: Enter.\n", __func__); + pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name); + pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id); + pr_debug("%s: substream->name = %s.\n", __func__, substream->name); + pr_debug("%s: substream->number = %d.\n", __func__, substream->number); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS); + + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(codec) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_tdm_slot(codec_dai, + 1 << CG29XX_DAI_SLOT0_SHIFT, + 1 << CG29XX_DAI_SLOT0_SHIFT, + UX500_CG29XX_DAI_SLOTS, + UX500_CG29XX_DAI_SLOT_WIDTH); + + if (err) { + pr_err("%s: cg29xx_set_tdm_slot(codec_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF); + + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_sysclk(cpu_dai, + UX500_MSP_MASTER_CLOCK, + UX500_CG29XX_MSP_CLOCK_FREQ, + 0); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_tdm_slot(cpu_dai, + UX500_CG29XX_DAI_ACTIVE_SLOTS, + UX500_CG29XX_DAI_ACTIVE_SLOTS, + UX500_CG29XX_DAI_SLOTS, + UX500_CG29XX_DAI_SLOT_WIDTH); + + if (err) { + pr_err("%s: cg29xx_set_tdm_slot(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + +out_err: + return err; +} + +int u5500_cg29xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int channels = params_channels(params); + int err; + struct snd_soc_codec *codec = codec_dai->codec; + int dai_id = codec_dai->id; + struct cg29xx_codec_dai_data *codec_drvdata = + snd_soc_codec_get_drvdata(codec); + struct cg29xx_codec_dai_data *dai_data = &codec_drvdata[dai_id]; + + pr_debug("%s: Enter.\n", __func__); + pr_debug("%s: substream->pcm->name=%s\n", + __func__, substream->pcm->name); + pr_debug("%s: substream->pcm->id = %s\n", __func__, substream->pcm->id); + pr_debug("%s: substream->name = %s.\n", __func__, substream->name); + pr_debug("%s: substream->number = %d.\n", __func__, substream->number); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + if (dai_data->config.port == PORT_0_I2S) { + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (err) { + pr_err("%s: snd_soc_dai_set_fmt (codec) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + } else { + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS); + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(codec) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_tdm_slot(codec_dai, + 1 << CG29XX_DAI_SLOT0_SHIFT, + 1 << CG29XX_DAI_SLOT0_SHIFT, + UX500_CG29XX_DAI_SLOTS, + UX500_CG29XX_DAI_SLOT_WIDTH); + + if (err) { + pr_err("%s: cg29xx_set_tdm_slot(codec_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF); + + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_sysclk(cpu_dai, + UX500_MSP_MASTER_CLOCK, + U5500_CG29XX_MSP_CLOCK_FREQ, + 0); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_tdm_slot(cpu_dai, + UX500_CG29XX_DAI_ACTIVE_SLOTS, + UX500_CG29XX_DAI_ACTIVE_SLOTS, + UX500_CG29XX_DAI_SLOTS, + UX500_CG29XX_DAI_SLOT_WIDTH); + + if (err) { + pr_err("%s: cg29xx_set_tdm_slot(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + } + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_0); +out_err: + return err; +} + +struct snd_soc_ops ux500_cg29xx_ops[] = { + { + .hw_params = ux500_cg29xx_hw_params, + } +}; + +struct snd_soc_ops u5500_cg29xx_ops[] = { + { + .hw_params = u5500_cg29xx_hw_params, + } +}; + diff --git a/sound/soc/ux500/ux500_cg29xx.h b/sound/soc/ux500/ux500_cg29xx.h new file mode 100644 index 00000000000..33736bca0cd --- /dev/null +++ b/sound/soc/ux500/ux500_cg29xx.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ola Lilja <ola.o.lilja@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. + */ + +#ifndef UX500_CG29XX_H +#define UX500_CG29XX_H + +extern struct snd_soc_ops ux500_cg29xx_ops[]; +extern struct snd_soc_ops u5500_cg29xx_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c new file mode 100644 index 00000000000..5eabb0629d7 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -0,0 +1,947 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: 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/slab.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> + +#include <mach/hardware.h> +#include <mach/msp.h> + +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "ux500_msp_i2s.h" +#include "ux500_msp_dai.h" +#include "ux500_pcm.h" + +static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ, + }, +}; + +static const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +static int ux500_msp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if ((mode_playback && drvdata->playback_active) || + (!mode_playback && drvdata->capture_active)) { + pr_err("%s: Error: MSP %d (%s): Stream already active.\n", + __func__, + dai->id, + stream_str(substream)); + return -EBUSY; + } + + if (mode_playback) + drvdata->playback_active = true; + else + drvdata->capture_active = true; + + return 0; +} + +static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if (drvdata == NULL) + return; + + if (ux500_msp_i2s_close(drvdata->msp_i2s_drvdata, + mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE)) { + pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, + dai->id, + stream_str(substream)); + } + + if (mode_playback) { + drvdata->playback_active = false; + drvdata->configured &= ~PLAYBACK_CONFIGURED; + } else { + drvdata->capture_active = false; + drvdata->configured &= ~CAPTURE_CONFIGURED; + } +} + +static void ux500_msp_dai_setup_multichannel(struct ux500_platform_drvdata *private, + struct msp_config *msp_config) +{ + struct msp_multichannel_config *multi = &msp_config->multichannel_config; + + if (private->slots > 1) { + msp_config->multichannel_configured = 1; + + multi->tx_multichannel_enable = true; + multi->rx_multichannel_enable = true; + multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED; + + multi->tx_channel_0_enable = private->tx_mask; + multi->tx_channel_1_enable = 0; + multi->tx_channel_2_enable = 0; + multi->tx_channel_3_enable = 0; + + multi->rx_channel_0_enable = private->rx_mask; + multi->rx_channel_1_enable = 0; + multi->rx_channel_2_enable = 0; + multi->rx_channel_3_enable = 0; + + pr_debug("%s: Multichannel enabled." + "Slots: %d TX: %u RX: %u\n", + __func__, + private->slots, + multi->tx_channel_0_enable, + multi->rx_channel_0_enable); + } +} + +static void ux500_msp_dai_setup_frameper(struct ux500_platform_drvdata *private, + unsigned int rate, + struct msp_protocol_desc *prot_desc) +{ + switch (private->slots) { + default: + case 1: + switch (rate) { + case 8000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_8_KHZ; + break; + case 16000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_16_KHZ; + break; + case 44100: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_44_1_KHZ; + break; + case 48000: + default: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_48_KHZ; + break; + } + break; + + case 2: + prot_desc->frame_period = FRAME_PER_2_SLOTS; + break; + + case 8: + prot_desc->frame_period = + FRAME_PER_8_SLOTS; + break; + + case 16: + prot_desc->frame_period = + FRAME_PER_16_SLOTS; + break; + } + + prot_desc->total_clocks_for_one_frame = + prot_desc->frame_period+1; + + pr_debug("%s: Total clocks per frame: %u\n", + __func__, + prot_desc->total_clocks_for_one_frame); +} + +static void ux500_msp_dai_setup_framing_pcm(struct ux500_platform_drvdata *private, + unsigned int rate, + struct msp_protocol_desc *prot_desc) +{ + u32 frame_length = MSP_FRAME_LENGTH_1; + prot_desc->frame_width = 0; + + switch (private->slots) { + default: + case 1: + frame_length = MSP_FRAME_LENGTH_1; + break; + + case 2: + frame_length = MSP_FRAME_LENGTH_2; + break; + + case 8: + frame_length = MSP_FRAME_LENGTH_8; + break; + + case 16: + frame_length = MSP_FRAME_LENGTH_16; + break; + } + + prot_desc->tx_frame_length_1 = frame_length; + prot_desc->rx_frame_length_1 = frame_length; + prot_desc->tx_frame_length_2 = frame_length; + prot_desc->rx_frame_length_2 = frame_length; + + prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16; + prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16; + prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16; + prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16; + + ux500_msp_dai_setup_frameper(private, rate, prot_desc); +} + +static void ux500_msp_dai_setup_clocking(unsigned int fmt, + struct msp_config *msp_config) +{ + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + default: + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_NB_IF: + msp_config->tx_frame_sync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_frame_sync_pol ^= 1 << RFSPOL_SHIFT; + break; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) { + pr_debug("%s: Codec is MASTER.\n", + __func__); + msp_config->iodelay = 0x20; + msp_config->rx_frame_sync_sel = 0; + msp_config->tx_frame_sync_sel = 1 << TFSSEL_SHIFT; + msp_config->tx_clock_sel = 0; + msp_config->rx_clock_sel = 0; + msp_config->srg_clock_sel = 0x2 << SCKSEL_SHIFT; + + } else { + pr_debug("%s: Codec is SLAVE.\n", + __func__); + + msp_config->tx_clock_sel = TX_CLK_SEL_SRG; + msp_config->tx_frame_sync_sel = TX_SYNC_SRG_PROG; + msp_config->rx_clock_sel = RX_CLK_SEL_SRG; + msp_config->rx_frame_sync_sel = RX_SYNC_SRG; + msp_config->srg_clock_sel = 1 << SCKSEL_SHIFT; + } +} + +static void ux500_msp_dai_compile_prot_desc_pcm(unsigned int fmt, + struct msp_protocol_desc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->tx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_HIGH); + prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_HIGH << RFSPOL_SHIFT; + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) { + pr_debug("%s: DSP_A.\n", + __func__); + prot_desc->rx_clock_pol = MSP_RISING_EDGE; + prot_desc->tx_clock_pol = MSP_FALLING_EDGE; + } else { + pr_debug("%s: DSP_B.\n", + __func__); + prot_desc->rx_clock_pol = MSP_FALLING_EDGE; + prot_desc->tx_clock_pol = MSP_RISING_EDGE; + } + + prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP; + prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI; + prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE; + prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE; +} + +static void ux500_msp_dai_compile_prot_desc_i2s(struct msp_protocol_desc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_DUAL_PHASE; + prot_desc->tx_phase_mode = MSP_DUAL_PHASE; + prot_desc->rx_phase2_start_mode = + MSP_PHASE2_START_MODE_FRAME_SYNC; + prot_desc->tx_phase2_start_mode = + MSP_PHASE2_START_MODE_FRAME_SYNC; + prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_LOW); + prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_LOW << RFSPOL_SHIFT; + + prot_desc->rx_frame_length_1 = MSP_FRAME_LENGTH_1; + prot_desc->rx_frame_length_2 = MSP_FRAME_LENGTH_1; + prot_desc->tx_frame_length_1 = MSP_FRAME_LENGTH_1; + prot_desc->tx_frame_length_2 = MSP_FRAME_LENGTH_1; + prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16; + prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16; + prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16; + prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16; + + prot_desc->rx_clock_pol = MSP_RISING_EDGE; + prot_desc->tx_clock_pol = MSP_FALLING_EDGE; + + prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP; + prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI; + prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE; + prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE; +} + +static void ux500_msp_dai_compile_msp_config(struct snd_pcm_substream *substream, + struct ux500_platform_drvdata *private, + unsigned int rate, + struct msp_config *msp_config) +{ + struct msp_protocol_desc *prot_desc = &msp_config->protocol_desc; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int fmt = private->fmt; + + memset(msp_config, 0, sizeof(*msp_config)); + + msp_config->input_clock_freq = private->master_clk; + + msp_config->tx_fifo_config = TX_FIFO_ENABLE; + msp_config->rx_fifo_config = RX_FIFO_ENABLE; + msp_config->spi_clk_mode = SPI_CLK_MODE_NORMAL; + msp_config->spi_burst_mode = 0; + msp_config->def_elem_len = 1; + msp_config->direction = + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MSP_TRANSMIT_MODE : MSP_RECEIVE_MODE; + msp_config->data_size = MSP_DATA_BITS_32; + msp_config->work_mode = MSP_DMA_MODE; + msp_config->frame_freq = rate; + + pr_debug("%s: input_clock_freq = %u, frame_freq = %u.\n", + __func__, msp_config->input_clock_freq, msp_config->frame_freq); + /* To avoid division by zero in I2S-driver (i2s_setup) */ + prot_desc->total_clocks_for_one_frame = 1; + + prot_desc->rx_data_delay = private->data_delay; + prot_desc->tx_data_delay = private->data_delay; + + pr_debug("%s: rate: %u channels: %d.\n", + __func__, + rate, + runtime->channels); + switch (fmt & + (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) { + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + pr_debug("%s: SND_SOC_DAIFMT_I2S.\n", + __func__); + + msp_config->default_protocol_desc = 1; + msp_config->protocol = MSP_I2S_PROTOCOL; + break; + + default: + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + pr_debug("%s: SND_SOC_DAIFMT_I2S.\n", + __func__); + + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_I2S_PROTOCOL; + + ux500_msp_dai_compile_prot_desc_i2s(prot_desc); + break; + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + pr_debug("%s: PCM format.\n", + __func__); + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_PCM_PROTOCOL; + + ux500_msp_dai_compile_prot_desc_pcm(fmt, prot_desc); + ux500_msp_dai_setup_multichannel(private, msp_config); + ux500_msp_dai_setup_framing_pcm(private, rate, prot_desc); + break; + } + + ux500_msp_dai_setup_clocking(fmt, msp_config); +} + +static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msp_config msp_config; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u8 configflag = mode_playback ? PLAYBACK_CONFIGURED : CAPTURE_CONFIGURED; + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if (configflag & drvdata->configured) { + + ret = ux500_msp_i2s_close( + drvdata->msp_i2s_drvdata, + mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE); + + if (ret) { + pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, + dai->id, + stream_str(substream)); + goto cleanup; + } + + drvdata->configured &= ~configflag; + } + + pr_debug("%s: Setup dai (Rate: %u).\n", __func__, runtime->rate); + ux500_msp_dai_compile_msp_config(substream, + drvdata, + runtime->rate, + &msp_config); + + ret = ux500_msp_i2s_open(drvdata->msp_i2s_drvdata, &msp_config); + if (ret < 0) { + pr_err("%s: Error: msp_setup failed (ret = %d)!\n", __func__, ret); + goto cleanup; + } + + drvdata->configured |= configflag; + +cleanup: + return ret; +} + +static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int mask, slots_active; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d (%s): Enter.\n", + __func__, + dai->id, + stream_str(substream)); + + switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + if (params_channels(params) != 2) { + pr_err("%s: Error: I2S requires channels = 2 " + "(channels = %d)!\n", + __func__, + params_channels(params)); + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_DSP_A: + + mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + drvdata->tx_mask : + drvdata->rx_mask; + + slots_active = hweight32(mask); + + pr_debug("TDM slots active: %d", slots_active); + + if (params_channels(params) != slots_active) { + pr_err("%s: Error: PCM TDM format requires channels " + "to match active slots " + "(channels = %d, active slots = %d)!\n", + __func__, + params_channels(params), + slots_active); + return -EINVAL; + } + break; + + default: + break; + } + + return 0; +} + +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id); + + switch (delay) { + case MSP_DELAY_0: + case MSP_DELAY_1: + case MSP_DELAY_2: + case MSP_DELAY_3: + break; + default: + goto unsupported_delay; + } + + drvdata->data_delay = delay; + return 0; + +unsupported_delay: + pr_err("%s: MSP %d: Error: Unsupported DAI delay (%d)!\n", + __func__, + dai->id, + delay); + return -EINVAL; +} + +static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id); + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + break; + + default: + goto unsupported_format; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + break; + + default: + goto unsupported_format; + } + + drvdata->fmt = fmt; + return 0; + +unsupported_format: + pr_err("%s: MSP %d: Error: Unsupported DAI format (0x%x)!\n", + __func__, + dai->id, + fmt); + return -EINVAL; +} + +static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, + int slot_width) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + unsigned int cap; + + if (!(slots == 1 || slots == 2 || slots == 8 || slots == 16)) { + pr_err("%s: Error: Unsupported slots (%d)! " + "Supported values are 1/2/8/16.\n", + __func__, + slots); + return -EINVAL; + } + drvdata->slots = slots; + + if (!(slot_width == 16)) { + pr_err("%s: Error: Unsupported slots_width (%d)!. " + "Supported value is 16.\n", + __func__, + slot_width); + return -EINVAL; + } + drvdata->slot_width = slot_width; + + switch (slots) { + default: + case 1: + cap = 0x01; + break; + case 2: + cap = 0x03; + break; + case 8: + cap = 0xFF; + break; + case 16: + cap = 0xFFFF; + break; + } + + drvdata->tx_mask = tx_mask & cap; + drvdata->rx_mask = rx_mask & cap; + + return 0; +} + +static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, + unsigned int freq, + int dir) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter. Clk id: %d, freq: %u.\n", + __func__, + dai->id, + clk_id, + freq); + + switch (clk_id) { + case UX500_MSP_MASTER_CLOCK: + drvdata->master_clk = freq; + break; + + default: + pr_err("%s: MSP %d: Invalid clkid: %d.\n", + __func__, + dai->id, + clk_id); + } + + return 0; +} + +static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n", + __func__, + dai->id, + stream_str(substream), + (int)drvdata->msp_i2s_drvdata->id, + cmd); + + ret = ux500_msp_i2s_trigger(drvdata->msp_i2s_drvdata, + cmd, + substream->stream); + + return ret; +} + +static int +ux500_msp_dai_probe(struct snd_soc_dai *dai) +{ + struct ux500_msp_i2s_drvdata *drvdata = snd_soc_dai_get_drvdata(dai); + struct ux500_platform_drvdata *private = &platform_drvdata[dai->id]; + + drvdata->playback_dma_data.dma_cfg = drvdata->msp->dma_cfg_tx; + drvdata->capture_dma_data.dma_cfg = drvdata->msp->dma_cfg_rx; + + dai->playback_dma_data = &drvdata->playback_dma_data; + dai->capture_dma_data = &drvdata->capture_dma_data; + + drvdata->playback_dma_data.data_size = private->slot_width; + drvdata->capture_dma_data.data_size = private->slot_width; + + return 0; +} + +static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { + { + .name = "ux500-msp-i2s.0", + .probe = ux500_msp_dai_probe, + .id = 0, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } + }, + }, + { + .name = "ux500-msp-i2s.1", + .probe = ux500_msp_dai_probe, + .id = 1, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } + }, + }, + { + .name = "ux500-msp-i2s.2", + .id = 2, + .probe = ux500_msp_dai_probe, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } + }, + }, + { + .name = "ux500-msp-i2s.3", + .probe = ux500_msp_dai_probe, + .id = 3, + .suspend = NULL, + .resume = NULL, + .playback = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .capture = { + .channels_min = UX500_MSP_MIN_CHANNELS, + .channels_max = UX500_MSP_MAX_CHANNELS, + .rates = UX500_I2S_RATES, + .formats = UX500_I2S_FORMATS, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } + }, + }, +}; +EXPORT_SYMBOL(ux500_msp_dai_drv); + +static int ux500_msp_drv_probe(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + struct ux500_platform_drvdata *drvdata; + struct msp_i2s_platform_data *platform_data; + int id; + int ret = 0; + + pr_err("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data; + msp_i2s_drvdata = ux500_msp_i2s_init(pdev, platform_data); + if (!msp_i2s_drvdata) { + pr_err("%s: ERROR: ux500_msp_i2s_init failed!", __func__); + return -EINVAL; + } + + id = msp_i2s_drvdata->id; + drvdata = &platform_drvdata[id]; + drvdata->msp_i2s_drvdata = msp_i2s_drvdata; + + pr_info("%s: Registering ux500-msp-dai SoC CPU-DAI.\n", __func__); + ret = snd_soc_register_dai(&pdev->dev, &ux500_msp_dai_drv[id]); + if (ret < 0) { + pr_err("Error: %s: Failed to register MSP %d.\n", __func__, id); + return ret; + } + + return ret; +} + +static int ux500_msp_drv_remove(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + struct ux500_platform_drvdata *drvdata = &platform_drvdata[msp_i2s_drvdata->id]; + + pr_info("%s: Unregister ux500-msp-dai ASoC CPU-DAI.\n", __func__); + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv)); + + ux500_msp_i2s_exit(msp_i2s_drvdata); + drvdata->msp_i2s_drvdata = NULL; + + return 0; +} + +int ux500_msp_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_suspend(msp_i2s_drvdata); +} + +int ux500_msp_drv_resume(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_resume(msp_i2s_drvdata); +} + +static struct platform_driver msp_i2s_driver = { + .driver = { + .name = "ux500-msp-i2s", + .owner = THIS_MODULE, + }, + .probe = ux500_msp_drv_probe, + .remove = ux500_msp_drv_remove, + .suspend = ux500_msp_drv_suspend, + .resume = ux500_msp_drv_resume, +}; + +static int __init ux500_msp_init(void) +{ + pr_info("%s: Register ux500-msp-dai platform driver.\n", __func__); + return platform_driver_register(&msp_i2s_driver); +} + +static void __exit ux500_msp_exit(void) +{ + pr_info("%s: Unregister ux500-msp-dai platform driver.\n", __func__); + platform_driver_unregister(&msp_i2s_driver); +} + +module_init(ux500_msp_init); +module_exit(ux500_msp_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h new file mode 100644 index 00000000000..f5bda875214 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: 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. + */ + +#ifndef UX500_msp_dai_H +#define UX500_msp_dai_H + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <mach/msp.h> + +#define UX500_NBR_OF_DAI 4 + +#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +#define FRAME_PER_SINGLE_SLOT_8_KHZ 31 +#define FRAME_PER_SINGLE_SLOT_16_KHZ 124 +#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63 +#define FRAME_PER_SINGLE_SLOT_48_KHZ 49 +#define FRAME_PER_2_SLOTS 31 +#define FRAME_PER_8_SLOTS 138 +#define FRAME_PER_16_SLOTS 277 + +#ifndef CONFIG_SND_SOC_UX500_AB5500 +#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000 +#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ +#else +#define UX500_MSP_INTERNAL_CLOCK_FREQ 13000000 +#define UX500_MSP1_INTERNAL_CLOCK_FREQ (UX500_MSP_INTERNAL_CLOCK_FREQ * 2) +#endif + +#define UX500_MSP_MIN_CHANNELS 1 +#define UX500_MSP_MAX_CHANNELS 8 + +#define PLAYBACK_CONFIGURED 1 +#define CAPTURE_CONFIGURED 2 + +enum ux500_msp_clock_id { + UX500_MSP_MASTER_CLOCK, +}; + +struct ux500_platform_drvdata { + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + unsigned int fmt; + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; + bool playback_active; + bool capture_active; + u8 configured; + int data_delay; + unsigned int master_clk; +}; + +extern struct snd_soc_dai ux500_msp_dai[UX500_NBR_OF_DAI]; +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); + +#endif diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c new file mode 100644 index 00000000000..71d40e16e2c --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,837 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com>, + * Sandeep Kaushik <sandeep.kaushik@st.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/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> +#include <mach/msp.h> + +#include <sound/soc.h> + +#include "ux500_msp_i2s.h" + +static int +ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config); + + /* Protocol desciptors */ +static const struct msp_protocol_desc prot_descs[] = { + I2S_PROTOCOL_DESC, + PCM_PROTOCOL_DESC, + PCM_COMPAND_PROTOCOL_DESC, + AC97_PROTOCOL_DESC, + SPI_MASTER_PROTOCOL_DESC, + SPI_SLAVE_PROTOCOL_DESC, +}; + +static void ux500_msp_i2s_set_prot_desc_tx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2); + if (protocol_desc->tx_element_length_1 == + protocol_desc->tx_element_length_2) { + msp->actual_data_size = protocol_desc->tx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_TCF); +} + +static void ux500_msp_i2s_set_prot_desc_rx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2); + if (protocol_desc->rx_element_length_1 == + protocol_desc->rx_element_length_2) { + msp->actual_data_size = protocol_desc->rx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_RCF); + +} + +static int ux500_msp_i2s_configure_protocol(struct msp *msp, + struct msp_config *config) +{ + int direction; + struct msp_protocol_desc *protocol_desc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + direction = config->direction; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol!\n", __func__); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) { + pr_err("%s: ERROR: Invalid data-size requested (data_size = %d)!\n", + __func__, data_size); + return -EINVAL; + } + + switch (direction) { + case MSP_TRANSMIT_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + break; + case MSP_RECEIVE_MODE: + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + case MSP_BOTH_T_R_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + default: + pr_err("%s: ERROR: Invalid direction requested (direction = %d)!\n", + __func__, direction); + return -EINVAL; + } + + /* The below code is needed for both Rx and Tx path. Can't separate them. */ + temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(~protocol_desc->tx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +static int ux500_msp_i2s_configure_clock(struct msp *msp, struct msp_config *config) +{ + u32 reg_val_GCR; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + u32 bit_clock = 0; + struct msp_protocol_desc *protocol_desc = NULL; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); + + if (config->default_protocol_desc) + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + else + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_AC97_PROTOCOL: + /* Not supported */ + pr_err("%s: ERROR: AC97 protocol not supported!\n", __func__); + return -ENOSYS; + default: + pr_err("%s: ERROR: Unknown protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + writel(temp_reg, msp->registers + MSP_SRG); + + bit_clock = (config->input_clock_freq)/(sck_div + 1); + /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP + * Only consider OPP 100% when bit-clock is used, i.e. MSP master mode + */ + if ((bit_clock > 19200000) && ((config->tx_clock_sel != 0) || (config->rx_clock_sel != 0))) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100); + msp->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 50); + msp->vape_opp_constraint = 0; + } + + /* Enable clock */ + udelay(100); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR); + udelay(100); + + return 0; +} + +static int ux500_msp_i2s_configure_multichannel(struct msp *msp, struct msp_config *config) +{ + struct msp_protocol_desc *protocol_desc; + struct msp_multichannel_config *mcfg; + u32 reg_val_MCR; + + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + protocol_desc = (struct msp_protocol_desc *) + &prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + mcfg = &config->multichannel_config; + if (mcfg->tx_multichannel_enable) { + if (protocol_desc->tx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->tx_multichannel_enable ? 1 << TMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->tx_channel_0_enable, + msp->registers + MSP_TCE0); + writel(mcfg->tx_channel_1_enable, + msp->registers + MSP_TCE1); + writel(mcfg->tx_channel_2_enable, + msp->registers + MSP_TCE2); + writel(mcfg->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (TX-mode: %d)!\n", + __func__, protocol_desc->tx_phase_mode); + return -EINVAL; + } + } + if (mcfg->rx_multichannel_enable) { + if (protocol_desc->rx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_multichannel_enable ? 1 << RMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->rx_channel_0_enable, + msp->registers + MSP_RCE0); + writel(mcfg->rx_channel_1_enable, + msp->registers + MSP_RCE1); + writel(mcfg->rx_channel_2_enable, + msp->registers + MSP_RCE2); + writel(mcfg->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (RX-mode: %d)!\n", + __func__, protocol_desc->rx_phase_mode); + return -EINVAL; + } + if (mcfg->rx_comparison_enable_mode) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_comparison_enable_mode << RCMPM_BIT), + msp->registers + MSP_MCR); + + writel(mcfg->comparison_mask, + msp->registers + MSP_RCM); + writel(mcfg->comparison_value, + msp->registers + MSP_RCV); + + } + } + + return 0; +} + +static int ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config) +{ + int status = 0; + u32 reg_val_DMACR, reg_val_GCR; + + if (config->work_mode != MSP_DMA_MODE) { + pr_err("%s: ERROR: Only DMA-mode is supported (msp->work_mode = %d)\n", + __func__, + msp->work_mode); + return -EINVAL; + } + msp->work_mode = config->work_mode; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + if (msp->msp_state == MSP_STATE_IDLE) { + if (msp->plat_init) { + status = msp->plat_init(); + if (status) { + pr_err("%s: ERROR: Failed to init MSP (%d)!\n", + __func__, + status); + return status; + } + } + } + + /* Configure msp with protocol dependent settings */ + ux500_msp_i2s_configure_protocol(msp, config); + ux500_msp_i2s_configure_clock(msp, config); + if (config->multichannel_configured == 1) { + status = ux500_msp_i2s_configure_multichannel(msp, config); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_configure_multichannel failed (%d)!\n", + __func__, status); + } + + /* Make sure the correct DMA-directions are configured */ + if ((config->direction == MSP_RECEIVE_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_rx) { + pr_err("%s: ERROR: MSP RX-mode is not configured!", __func__); + return -EINVAL; + } + if ((config->direction == MSP_TRANSMIT_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_tx) { + pr_err("%s: ERROR: MSP TX-mode is not configured!", __func__); + return -EINVAL; + } + + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + switch (config->direction) { + case MSP_TRANSMIT_MODE: + writel(reg_val_DMACR | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + break; + case MSP_RECEIVE_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + break; + case MSP_BOTH_T_R_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + break; + default: + pr_err("%s: ERROR: Illegal MSP direction (config->direction = %d)!", + __func__, + config->direction); + if (msp->plat_exit) + msp->plat_exit(); + return -EINVAL; + } + writel(config->iodelay, msp->registers + MSP_IODLY); + + /* Enable frame generation logic */ + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); + + return status; +} + +static void flush_fifo_rx(struct msp *msp) +{ + u32 reg_val_DR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) { + reg_val_DR = readl(msp->registers + MSP_DR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +static void flush_fifo_tx(struct msp *msp) +{ + u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR); + writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) { + reg_val_TSTDR = readl(msp->registers + MSP_TSTDR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + writel(0x0, msp->registers + MSP_ITCR); + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config) +{ + struct msp *msp = drvdata->msp; + u32 old_reg, new_reg, mask; + int res; + + if (in_interrupt()) { + pr_err("%s: ERROR: Open called in interrupt context!\n", __func__); + return -1; + } + + /* Two simultanous configuring msp is avoidable */ + down(&msp->lock); + + /* Don't enable regulator if its msp1 or msp3 */ + if (!(msp->reg_enabled) && msp->id != MSP_1_I2S_CONTROLLER + && msp->id != MSP_3_I2S_CONTROLLER) { + res = regulator_enable(drvdata->reg_vape); + if (res != 0) { + pr_err("%s: Failed to enable regulator!\n", __func__); + up(&msp->lock); + return res; + } + msp->reg_enabled = 1; + } + + switch (msp->users) { + case 0: + clk_enable(msp->clk); + msp->direction = msp_config->direction; + break; + case 1: + if (msp->direction == MSP_BOTH_T_R_MODE || + msp_config->direction == msp->direction || + msp_config->direction == MSP_BOTH_T_R_MODE) { + pr_warn("%s: WARN: MSP is in use (direction = %d)!\n", + __func__, msp_config->direction); + up(&msp->lock); + return -EBUSY; + } + msp->direction = MSP_BOTH_T_R_MODE; + break; + default: + pr_warn("%s: MSP in use in (both directions)!\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->users++; + + /* First do the global config register */ + mask = + RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK | + TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = (msp_config->tx_clock_sel | msp_config->rx_clock_sel | + msp_config->rx_frame_sync_pol | msp_config->tx_frame_sync_pol | + msp_config->rx_frame_sync_sel | msp_config->tx_frame_sync_sel | + msp_config->rx_fifo_config | msp_config->tx_fifo_config | + msp_config->srg_clock_sel | msp_config->loopback_enable | + msp_config->tx_data_enable); + + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + + if (ux500_msp_i2s_enable(msp, msp_config) != 0) { + pr_err("%s: ERROR: ux500_msp_i2s_enable failed!\n", __func__); + return -EBUSY; + } + if (msp_config->loopback_enable & 0x80) + msp->loopback_enable = 1; + + /* Flush FIFOs */ + flush_fifo_tx(msp); + flush_fifo_rx(msp); + + msp->msp_state = MSP_STATE_CONFIGURED; + up(&msp->lock); + return 0; +} + +static void ux500_msp_i2s_disable_rx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(RECEIVE_SERVICE_INT | RECEIVE_OVERRUN_ERROR_INT), + msp->registers + MSP_IMSC); +} + +static void ux500_msp_i2s_disable_tx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(TRANSMIT_SERVICE_INT | TRANSMIT_UNDERRUN_ERR_INT), + msp->registers + MSP_IMSC); +} + +static int ux500_msp_i2s_disable(struct msp *msp, int direction, enum i2s_flag flag) +{ + u32 reg_val_GCR; + int status = 0; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + + if (flag == DISABLE_TRANSMIT) + ux500_msp_i2s_disable_tx(msp); + else if (flag == DISABLE_RECEIVE) + ux500_msp_i2s_disable_rx(msp); + else { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | LOOPBACK_MASK, + msp->registers + MSP_GCR); + + /* Flush TX-FIFO */ + flush_fifo_tx(msp); + + /* Disable TX-channel */ + writel((readl(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + + /* Flush RX-FIFO */ + flush_fifo_rx(msp); + + /* Disable Loopback and Receive channel */ + writel((readl(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + + ux500_msp_i2s_disable_tx(msp); + ux500_msp_i2s_disable_rx(msp); + + } + + /* disable sample rate and frame generators */ + if (flag == DISABLE_ALL) { + msp->msp_state = MSP_STATE_IDLE; + writel((readl(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", + __func__, status); + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_TCF); + writel(0, msp->registers + MSP_RCF); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + writel(0, msp->registers + MSP_RCM); + writel(0, msp->registers + MSP_RCV); + writel(0, msp->registers + MSP_TCE0); + writel(0, msp->registers + MSP_TCE1); + writel(0, msp->registers + MSP_TCE2); + writel(0, msp->registers + MSP_TCE3); + writel(0, msp->registers + MSP_RCE0); + writel(0, msp->registers + MSP_RCE1); + writel(0, msp->registers + MSP_RCE2); + writel(0, msp->registers + MSP_RCE3); + } + + return status; +} + +int +ux500_msp_i2s_trigger( + struct ux500_msp_i2s_drvdata *drvdata, + int cmd, + int direction) +{ + struct msp *msp = drvdata->msp; + u32 reg_val_GCR, enable_bit; + + if (msp->msp_state == MSP_STATE_IDLE) { + pr_err("%s: ERROR: MSP is not configured!\n", __func__); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + enable_bit = TX_ENABLE; + } else { + enable_bit = RX_ENABLE; + } + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + ux500_msp_i2s_disable_tx(msp); + } else { + ux500_msp_i2s_disable_rx(msp); + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter.\n", __func__); + + down(&msp->lock); + + if (msp->users == 0) { + pr_err("%s: ERROR: MSP already closed!\n", __func__); + status = -EINVAL; + goto end; + } + pr_debug("%s: msp->users = %d, flag = %d\n", __func__, msp->users, flag); + + /* We need to call it twice for DISABLE_ALL*/ + msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1; + if (msp->users) + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, flag); + else { + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL); + clk_disable(msp->clk); + if (msp->reg_enabled) { + status = regulator_disable(drvdata->reg_vape); + msp->reg_enabled = 0; + } + if (status != 0) { + pr_err("%s: ERROR: Failed to disable regulator (%d)!\n", + __func__, status); + clk_enable(msp->clk); + goto end; + } + } + if (status) + goto end; + if (msp->users) + msp->direction = flag == DISABLE_TRANSMIT ? + MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE; + + if (msp->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s", 50); + msp->vape_opp_constraint = 0; + } +end: + up(&msp->lock); + return status; + +} + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + int irq; + struct resource *res = NULL; + struct i2s_controller *i2s_cont; + struct msp *msp; + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + msp_i2s_drvdata = kzalloc(sizeof(struct ux500_msp_i2s_drvdata), GFP_KERNEL); + msp_i2s_drvdata->msp = kzalloc(sizeof(struct msp), GFP_KERNEL); + msp = msp_i2s_drvdata->msp; + + msp->id = platform_data->id; + msp_i2s_drvdata->id = msp->id; + pr_debug("msp_i2s_drvdata->id = %d\n", msp_i2s_drvdata->id); + + msp->plat_init = platform_data->msp_i2s_init; + msp->plat_exit = platform_data->msp_i2s_exit; + msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; + msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; + + sema_init(&msp->lock, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + pr_err("%s: ERROR: Unable to get resource!\n", __func__); + goto free_msp; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto free_msp; + msp->irq = irq; + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) + goto free_msp; + + msp_i2s_drvdata->reg_vape = regulator_get(NULL, "v-ape"); + if (IS_ERR(msp_i2s_drvdata->reg_vape)) { + pr_err("%s: ERROR: Failed to get Vape supply (%d)!\n", + __func__, (int)PTR_ERR(msp_i2s_drvdata->reg_vape)); + goto free_irq; + } + dev_set_drvdata(&pdev->dev, msp_i2s_drvdata); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, + PRCMU_QOS_DEFAULT_VALUE); + msp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(msp->clk)) { + pr_err("%s: ERROR: clk_get failed (%d)!\n", + __func__, (int)PTR_ERR(msp->clk)); + goto free_irq; + } + + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + /* I2S Controller is allocated and added in I2S controller class. */ + i2s_cont = kzalloc(sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + pr_err("%s: ERROR: Failed to allocate struct i2s_cont (kzalloc)!\n", + __func__); + goto del_clk; + } + i2s_cont->dev.parent = &pdev->dev; + i2s_cont->data = (void *)msp; + i2s_cont->id = (s16)msp->id; + snprintf(i2s_cont->name, + sizeof(i2s_cont->name), + "ux500-msp-i2s.%04x", + msp->id); + pr_debug("I2S device-name :%s\n", i2s_cont->name); + msp->i2s_cont = i2s_cont; + + return msp_i2s_drvdata; + +del_clk: + clk_put(msp->clk); +free_irq: + iounmap(msp->registers); +free_msp: + kfree(msp); + return NULL; +} + +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + device_unregister(&msp->i2s_cont->dev); + clk_put(msp->clk); + iounmap(msp->registers); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); + regulator_put(drvdata->reg_vape); + kfree(msp); + + return status; +} + +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + down(&msp->lock); + if (msp->users > 0) { + up(&msp->lock); + return -EBUSY; + } + up(&msp->lock); + + return 0; +} + +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata) +{ + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + return 0; +} + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h new file mode 100644 index 00000000000..a3a2338a8b1 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@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. + */ + + +#ifndef UX500_MSP_I2S_H +#define UX500_MSP_I2S_H + +#include <linux/platform_device.h> +#include <mach/msp.h> +#include "ux500_pcm.h" + +struct ux500_msp_i2s_drvdata { + int id; + struct msp *msp; + struct regulator *reg_vape; + struct ux500_pcm_dma_params playback_dma_data; + struct ux500_pcm_dma_params capture_dma_data; +}; + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data); +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config); +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag); +int ux500_msp_i2s_trigger(struct ux500_msp_i2s_drvdata *drvdata, int cmd, int direction); +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata); + +#endif + diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c new file mode 100644 index 00000000000..7616bbb09af --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.c @@ -0,0 +1,570 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: 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 <asm/page.h> + +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <plat/ste_dma40.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "ux500_pcm.h" + +static struct snd_pcm_hardware ux500_pcm_hw_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_BE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = UX500_PLATFORM_MIN_RATE_PLAYBACK, + .rate_max = UX500_PLATFORM_MAX_RATE_PLAYBACK, + .channels_min = UX500_PLATFORM_MIN_CHANNELS, + .channels_max = UX500_PLATFORM_MAX_CHANNELS, + .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN, + .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX, + .periods_min = UX500_PLATFORM_PERIODS_MIN, + .periods_max = UX500_PLATFORM_PERIODS_MAX, +}; + +static struct snd_pcm_hardware ux500_pcm_hw_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_BE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = UX500_PLATFORM_MIN_RATE_CAPTURE, + .rate_max = UX500_PLATFORM_MAX_RATE_CAPTURE, + .channels_min = UX500_PLATFORM_MIN_CHANNELS, + .channels_max = UX500_PLATFORM_MAX_CHANNELS, + .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN, + .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX, + .periods_min = UX500_PLATFORM_PERIODS_MIN, + .periods_max = UX500_PLATFORM_PERIODS_MAX, +}; + +static const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +static void +ux500_pcm_dma_eot_handler(void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime; + struct ux500_pcm_private *private; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, + dai->id, + stream_str(substream)); + + if (substream) { + runtime = substream->runtime; + private = substream->runtime->private_data; + + /* calc the offset in the circular buffer */ + private->offset += frames_to_bytes(runtime, + runtime->period_size); + private->offset %= frames_to_bytes(runtime, + runtime->period_size) * runtime->periods; + + snd_pcm_period_elapsed(substream); + } +} + +static int +ux500_pcm_dma_start( + struct snd_pcm_substream *substream, + dma_addr_t dma_addr, + int period_cnt, + size_t period_len, + int dai_idx, + int stream_id) +{ + dma_cookie_t status_submit; + int direction; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct ux500_pcm_private *private = runtime->private_data; + struct dma_async_tx_descriptor *cdesc; + struct ux500_pcm_dma_params *dma_params; + + pr_debug("%s: Enter\n", __func__); + + dma_params = snd_soc_dai_get_dma_data(dai, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + direction = DMA_TO_DEVICE; + else + direction = DMA_FROM_DEVICE; + + cdesc = private->pipeid->device->device_prep_dma_cyclic( + private->pipeid, + dma_addr, + period_cnt * period_len, + period_len, + direction); + + if (IS_ERR(cdesc)) { + pr_err("%s: ERROR: device_prep_dma_cyclic failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return -EINVAL; + } + + cdesc->callback = ux500_pcm_dma_eot_handler; + cdesc->callback_param = substream; + + status_submit = dmaengine_submit(cdesc); + + if (dma_submit_error(status_submit)) { + pr_err("%s: ERROR: dmaengine_submit failed!\n", __func__); + return -EINVAL; + } + + dma_async_issue_pending(private->pipeid); + + return 0; +} + +static void +ux500_pcm_dma_stop(struct work_struct *work) +{ + struct ux500_pcm_private *private = container_of(work, + struct ux500_pcm_private, ws_stop); + + if (private->pipeid != NULL) { + dmaengine_terminate_all(private->pipeid); + dma_release_channel(private->pipeid); + private->pipeid = NULL; + } +} + +static void +ux500_pcm_dma_hw_free( + struct device *dev, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + + if (runtime->dma_area == NULL) + return; + + if (buf != &substream->dma_buffer) { + dma_free_coherent( + buf->dev.dev, + buf->bytes, + buf->area, + buf->addr); + kfree(runtime->dma_buffer_p); + } + + snd_pcm_set_runtime_buffer(substream, NULL); +} + +static int ux500_pcm_open(struct snd_pcm_substream *substream) +{ + int stream_id = substream->pstr->stream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_pcm_private *private; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret; + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, + dai->id, + stream_str(substream)); + + pr_debug("%s: Set runtime hwparams.\n", __func__); + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_playback); + else + snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_capture); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer( + runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + if (ret < 0) { + pr_err("%s: Error: snd_pcm_hw_constraints failed (%d)\n", + __func__, + ret); + return ret; + } + + pr_debug("%s: Init runtime private data.\n", __func__); + private = kzalloc(sizeof(struct ux500_pcm_private), GFP_KERNEL); + if (private == NULL) + return -ENOMEM; + private->msp_id = dai->id; + private->wq = alloc_ordered_workqueue("ux500/pcm", 0); + INIT_WORK(&private->ws_stop, ux500_pcm_dma_stop); + + runtime->private_data = private; + + pr_debug("%s: Set hw-struct for %s.\n", + __func__, + stream_str(substream)); + + runtime->hw = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + ux500_pcm_hw_playback : ux500_pcm_hw_capture; + + return 0; +} + +static int ux500_pcm_close(struct snd_pcm_substream *substream) +{ + struct ux500_pcm_private *private = substream->runtime->private_data; + + pr_debug("%s: Enter\n", __func__); + + if (private->pipeid != NULL) { + dma_release_channel(private->pipeid); + private->pipeid = NULL; + } + + destroy_workqueue(private->wq); + kfree(private); + + return 0; +} + +static int ux500_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + int ret = 0; + int size; + + pr_debug("%s: Enter\n", __func__); + + size = params_buffer_bytes(hw_params); + + if (buf) { + if (buf->bytes >= size) + goto out; + ux500_pcm_dma_hw_free(NULL, substream); + } + + if (substream->dma_buffer.area != NULL && + substream->dma_buffer.bytes >= size) { + buf = &substream->dma_buffer; + } else { + buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL); + if (!buf) + goto nomem; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = NULL; + buf->area = dma_alloc_coherent( + NULL, + size, + &buf->addr, + GFP_KERNEL); + buf->bytes = size; + buf->private_data = NULL; + + if (!buf->area) + goto free; + } + snd_pcm_set_runtime_buffer(substream, buf); + ret = 1; + out: + runtime->dma_bytes = size; + return ret; + + free: + kfree(buf); + nomem: + return -ENOMEM; +} + +static int +ux500_pcm_hw_free(struct snd_pcm_substream *substream) +{ + pr_debug("%s: Enter\n", __func__); + + ux500_pcm_dma_hw_free(NULL, substream); + + return 0; +} + +static int +ux500_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct stedma40_chan_cfg *dma_cfg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct ux500_pcm_private *private = runtime->private_data; + struct ux500_pcm_dma_params *dma_params; + dma_cap_mask_t mask; + u16 per_data_width, mem_data_width; + + pr_debug("%s: Enter\n", __func__); + + if (private->pipeid != NULL) { + dma_release_channel(private->pipeid); + private->pipeid = NULL; + } + + dma_params = snd_soc_dai_get_dma_data(dai, substream); + + mem_data_width = STEDMA40_HALFWORD_WIDTH; + + switch (dma_params->data_size) { + case 32: + per_data_width = STEDMA40_WORD_WIDTH; + break; + case 16: + per_data_width = STEDMA40_HALFWORD_WIDTH; + break; + case 8: + per_data_width = STEDMA40_BYTE_WIDTH; + break; + default: + per_data_width = STEDMA40_WORD_WIDTH; + pr_warn("%s: Unknown data-size (%d)! Assuming 32 bits.\n", + __func__, dma_params->data_size); + } + + dma_cfg = dma_params->dma_cfg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_cfg->src_info.data_width = mem_data_width; + dma_cfg->dst_info.data_width = per_data_width; + } else { + dma_cfg->src_info.data_width = per_data_width; + dma_cfg->dst_info.data_width = mem_data_width; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + private->pipeid = dma_request_channel(mask, stedma40_filter, dma_cfg); + + return 0; +} + +static int +ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_pcm_private *private = runtime->private_data; + int stream_id = substream->pstr->stream; + + pr_debug("%s: Enter\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: START/PAUSE-RELEASE\n", __func__); + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) { + pr_debug("XRUN occurred\n"); + return 0; + } + + private->offset = 0; + ret = ux500_pcm_dma_start( + substream, + runtime->dma_addr, + runtime->periods, + frames_to_bytes(runtime, runtime->period_size), + private->msp_id, + stream_id); + if (ret) { + pr_err("%s: Failed to configure I2S!\n", __func__); + return -EINVAL; + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + queue_work(private->wq, &private->ws_stop); + break; + + default: + pr_err("%s: Invalid command in pcm trigger\n", + __func__); + return -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t +ux500_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_pcm_private *private = runtime->private_data; + + pr_debug("%s: dma_offset %d frame %ld\n", __func__, private->offset, + bytes_to_frames(substream->runtime, private->offset)); + + return bytes_to_frames(substream->runtime, private->offset); +} + +static int +ux500_pcm_mmap( + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + pr_debug("%s: Enter.\n", __func__); + + return dma_mmap_coherent( + NULL, + vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops ux500_pcm_ops = { + .open = ux500_pcm_open, + .close = ux500_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ux500_pcm_hw_params, + .hw_free = ux500_pcm_hw_free, + .prepare = ux500_pcm_prepare, + .trigger = ux500_pcm_trigger, + .pointer = ux500_pcm_pointer, + .mmap = ux500_pcm_mmap +}; + +int ux500_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + pr_debug("%s: pcm = %d\n", __func__, (int)pcm); + + pcm->info_flags = 0; + strcpy(pcm->name, "UX500_PCM"); + + pr_debug("%s: pcm->name = %s.\n", __func__, pcm->name); + + return 0; +} + +static void ux500_pcm_free(struct snd_pcm *pcm) +{ + pr_debug("%s: Enter\n", __func__); +} + +static int ux500_pcm_suspend(struct snd_soc_dai *dai) +{ + pr_debug("%s: Enter\n", __func__); + + return 0; +} + +static int ux500_pcm_resume(struct snd_soc_dai *dai) +{ + pr_debug("%s: Enter\n", __func__); + + return 0; +} + +struct snd_soc_platform_driver ux500_pcm_soc_drv = { + .ops = &ux500_pcm_ops, + .pcm_new = ux500_pcm_new, + .pcm_free = ux500_pcm_free, + .suspend = ux500_pcm_suspend, + .resume = ux500_pcm_resume, +}; +EXPORT_SYMBOL(ux500_pcm_soc_drv); + +static int __devexit ux500_pcm_drv_probe(struct platform_device *pdev) +{ + int ret; + + pr_info("%s: Register ux500-pcm SoC platform driver.\n", __func__); + ret = snd_soc_register_platform(&pdev->dev, &ux500_pcm_soc_drv); + if (ret < 0) { + pr_err("%s: Error: Failed to register " + "ux500-pcm SoC platform driver (%d)!\n", + __func__, + ret); + return ret; + } + + return 0; +} + +static int __devinit ux500_pcm_drv_remove(struct platform_device *pdev) +{ + pr_info("%s: Unregister ux500-pcm SoC platform driver.\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver ux500_pcm_driver = { + .driver = { + .name = "ux500-pcm", + .owner = THIS_MODULE, + }, + + .probe = ux500_pcm_drv_probe, + .remove = __devexit_p(ux500_pcm_drv_remove), +}; + +static int __init ux500_pcm_drv_init(void) +{ + pr_debug("%s: Register ux500-pcm platform driver.\n", __func__); + + return platform_driver_register(&ux500_pcm_driver); +} + +static void __exit ux500_pcm_drv_exit(void) +{ + pr_debug("%s: Unregister ux500-pcm platform driver.\n", __func__); + + platform_driver_unregister(&ux500_pcm_driver); +} + +module_init(ux500_pcm_drv_init); +module_exit(ux500_pcm_drv_exit); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ux500/ux500_pcm.h b/sound/soc/ux500/ux500_pcm.h new file mode 100644 index 00000000000..3d1ea4f8395 --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: 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. + */ +#ifndef UX500_PCM_H +#define UX500_PCM_H + +#include <asm/page.h> +#include <linux/workqueue.h> + +#define UX500_PLATFORM_MIN_RATE_PLAYBACK 8000 +#define UX500_PLATFORM_MAX_RATE_PLAYBACK 48000 +#define UX500_PLATFORM_MIN_RATE_CAPTURE 8000 +#define UX500_PLATFORM_MAX_RATE_CAPTURE 48000 + +#define UX500_PLATFORM_MIN_CHANNELS 1 +#define UX500_PLATFORM_MAX_CHANNELS 8 + +#define UX500_PLATFORM_PERIODS_BYTES_MIN 128 +#define UX500_PLATFORM_PERIODS_BYTES_MAX (64 * PAGE_SIZE) +#define UX500_PLATFORM_PERIODS_MIN 2 +#define UX500_PLATFORM_PERIODS_MAX 48 +#define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE) + +extern struct snd_soc_platform ux500_soc_platform; + +struct ux500_pcm_private { + struct dma_chan *pipeid; + struct workqueue_struct *wq; + struct work_struct ws_stop; + int msp_id; + int stream_id; + unsigned int offset; +}; + +struct ux500_pcm_dma_params { + unsigned int data_size; + struct stedma40_chan_cfg *dma_cfg; +}; + +#endif |