diff options
author | Philippe Langlais <philippe.langlais@linaro.org> | 2011-03-25 10:50:33 +0100 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@stericsson.com> | 2011-09-19 15:14:42 +0200 |
commit | 80067c6f05647a405bbd1cc7748d9651971012e7 (patch) | |
tree | a132205994b08b791fc043825b488ad87f736a17 | |
parent | 91992f9884da3faa10056a8284428fce371895cc (diff) |
sound: add asoc codec support for ux500 platform
This patch is based on the following work:
cg29xx ASoc: The codec driver for cg29xx does now use the MFD driver for that chip.
ST-Ericsson ID: 259100
Author: Roger Nilsson <roger.xr.nilsson@stericsson.com>
Ux500 ASoC: HDMI-driver in Ux500 ALSA SoC-driver.
Author: Ola Lilja <ola.o.lilja@stericsson.com>
Add a power management scheme for AB3550 and fix bugs that hinder simultaneous playback/capture.
ST-Ericsson ID: WP 259100
Author: Xie Xiaolei <xie.xiaolei@stericsson.com>
sound: asoc: Added codec and machine drivers for cg29xx.
Author: Ola Lilja <ola.o.lilja@stericsson.com>
SND: sound codec ab3550 abx500 updates
Author: Mattias Wallin <mattias.wallin@stericsson.com>
New ASoC driver for Fairbanks.
Author: Ola Lilja <ola.o.lilja@stericsson.com>
Signed-off-by: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
Conflicts:
arch/arm/mach-ux500/Kconfig-arch
sound/soc/codecs/Makefile
-rw-r--r-- | sound/Kconfig | 20 | ||||
-rw-r--r-- | sound/soc/codecs/Kconfig | 12 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/codecs/ab3550.c | 1456 | ||||
-rw-r--r-- | sound/soc/codecs/ab3550.h | 336 | ||||
-rw-r--r-- | sound/soc/codecs/av8100_audio.c | 334 | ||||
-rw-r--r-- | sound/soc/codecs/av8100_audio.h | 22 | ||||
-rw-r--r-- | sound/soc/codecs/cg29xx.c | 778 | ||||
-rw-r--r-- | sound/soc/codecs/cg29xx.h | 44 |
9 files changed, 3008 insertions, 0 deletions
diff --git a/sound/Kconfig b/sound/Kconfig index c34965dbbec..35bb190ed54 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -48,6 +48,26 @@ choice endchoice +menu "Debug level for ux500 audio drivers" +config STM_ACODEC_DEBUG + int "STM ACODEC Debug Level" + depends on U8500_ACODEC + default 0 + help + Sets the ACODEC debug ON/OFF for U8500 SoC + * 0 OFF + * 1 ON + +config STM_ALSA_DEBUG + int "STM ALSA Debug Level" + depends on SND_U8500_ALSA || SND_U8500_ALSA_AB8500 + default 0 + help + Sets the ALSA debug ON/OFF for U8500 SoC + * 0 OFF + * 1 ON +endmenu + choice prompt "Driver mode" depends on U8500_ACODEC diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 98175a096df..d4e952e511d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -96,6 +96,9 @@ 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 + select SND_SOC_AB3550 + 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 @@ -371,6 +374,15 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate +config SND_SOC_AB3550 + 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 fd8558406ef..f774d716b6b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-88pm860x-objs := 88pm860x-codec.o +snd-soc-ab3550-objs := ab3550.o snd-soc-ac97-objs := ac97.o snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o @@ -10,6 +11,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-cs4270-objs := cs4270.o @@ -90,6 +93,7 @@ snd-soc-wm2000-objs := wm2000.o snd-soc-wm9090-objs := wm9090.o obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o +obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-ab3550.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o @@ -102,6 +106,8 @@ obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o 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_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_CS4270) += snd-soc-cs4270.o diff --git a/sound/soc/codecs/ab3550.c b/sound/soc/codecs/ab3550.c new file mode 100644 index 00000000000..e0a4c6d7f06 --- /dev/null +++ b/sound/soc/codecs/ab3550.c @@ -0,0 +1,1456 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <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 + +static struct device *ab3550_dev; +static struct snd_soc_codec *ab3550_codec; + +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 unsigned int ab3550_read_reg(struct snd_soc_codec *codec, + unsigned int reg); + +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; + } +} + +/** + This function is only called by the SOC framework to + set registers associated to the mixer controls. +*/ +static int ab3550_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_read_reg(struct snd_soc_codec *codec, + unsigned int reg) +{ + return read_reg(reg); +} + +static int ab3550_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ab3550_dapm_widgets, + ARRAY_SIZE(ab3550_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static void power_for_playback(enum enum_power onoff, int ifsel) +{ + 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_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 ab3550_codec_dai[] = { + { + .name = "ab3550_0", + .id = 0, + .playback = { + .stream_name = "ab3550_0", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "ab3550_0", + .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, + .private_data = NULL + }, + { + .name = "ab3550_1", + .id = 1, + .playback = { + .stream_name = "ab3550_1", + .channels_min = 2, + .channels_max = 2, + .rates = AB3550_SUPPORTED_RATE, + .formats = AB3550_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "ab3550_1", + .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, + .private_data = NULL + } +}; +EXPORT_SYMBOL_GPL(ab3550_codec_dai); + +static int ab3550_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + int ret; + + dev_info(&pdev->dev, "%s called. pdev = %p.\n", __func__, pdev); + if (!ab3550_codec) { + dev_err(&pdev->dev, "%s: Codec device not registered.\n", + __func__); + return -EAGAIN; + } + socdev->card->codec = ab3550_codec; + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "%s: Failed to create a new card " + "and new PCMs. error %d\n", __func__, ret); + goto err; + } + /* Add controls */ + if (ab3550_add_controls(ab3550_codec) < 0) + goto err; + ab3550_add_widgets(ab3550_codec); + return 0; + +err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int ab3550_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return 0; +} + +#ifdef CONFIG_PM +static int ab3550_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + dev_dbg(ab3550_dev, "%s : pdev=%p.\n", __func__, pdev); + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0); + return 0; +} + +static int ab3550_codec_resume(struct platform_device *pdev) +{ + dev_dbg(ab3550_dev, "%s : pdev=%p.\n", __func__, pdev); + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0xff); + return 0; +} +#else +#define ab3550_codec_resume NULL +#define ab3550_codec_suspend NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_ab3550 = { + .probe = ab3550_codec_probe, + .remove = ab3550_codec_remove, + .suspend = ab3550_codec_suspend, + .resume = ab3550_codec_resume +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_ab3550); + +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 int __init ab3550_platform_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + u8 reg; + + pr_debug("%s invoked with pdev = %p.\n", __func__, pdev); + 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)); + + ab3550_codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (ab3550_codec == NULL) + return -ENOMEM; + ab3550_codec->name = "AB3550"; + ab3550_codec->owner = THIS_MODULE; + ab3550_codec->dai = ab3550_codec_dai; + ab3550_codec->num_dai = 2; + ab3550_codec->read = ab3550_read_reg; + ab3550_codec->write = ab3550_write_reg; + ab3550_codec->reg_cache_size = 0; + ab3550_codec->reg_cache = NULL; + INIT_LIST_HEAD(&ab3550_codec->dapm_widgets); + INIT_LIST_HEAD(&ab3550_codec->dapm_paths); + mutex_init(&ab3550_codec->mutex); + ret = snd_soc_register_codec(ab3550_codec); + if (ret) { + dev_err(ab3550_dev, "%s: Failed to register codec: %d.\n", + __func__, ret); + kfree(ab3550_codec); + } + + for (i = 0; !ret && i < ARRAY_SIZE(ab3550_codec_dai); i++) + ret = snd_soc_register_dai(ab3550_codec_dai + i); + if (ret && i == 1) { + snd_soc_unregister_codec(ab3550_codec); + kfree(ab3550_codec); + } + + return ret; +} + +static int ab3550_platform_remove(struct platform_device *pdev) +{ + int i; + + pr_debug("%s called.\n", __func__); + mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0); + for (i = 0; i < ARRAY_SIZE(ab3550_codec_dai); i++) + snd_soc_unregister_dai(ab3550_codec_dai + i); + snd_soc_unregister_codec(ab3550_codec); + kfree(ab3550_codec); + ab3550_dev = NULL; + return 0; +} + +static int ab3550_platform_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int ab3550_platform_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ab3550_platform_driver = { + .driver = { + .name = "ab3550-codec", + .owner = THIS_MODULE, + }, + .probe = ab3550_platform_probe, + .remove = ab3550_platform_remove, + .suspend = ab3550_platform_suspend, + .resume = ab3550_platform_resume, +}; + + +static int __devinit ab3550_init(void) +{ + int ret1; + + pr_debug("%s called.\n", __func__); + + ab3550_dev = NULL; + ab3550_codec = NULL; + /* Register codec platform driver. */ + ret1 = platform_driver_register(&ab3550_platform_driver); + if (ret1 < 0) { + pr_debug("%s: Error %d: Failed to register codec platform " + "driver.\n", __func__, ret1); + } + return ret1; +} + +static void __devexit ab3550_exit(void) +{ + pr_debug("u8500_ab3550_init: Enter.\n"); + + /* Register codec platform driver. */ + pr_debug("%s: Un-register codec platform driver.\n", __func__); + platform_driver_unregister(&ab3550_platform_driver); +} + +module_init(ab3550_init); +module_exit(ab3550_exit); + +MODULE_DESCRIPTION("AB3550 Codec driver"); +MODULE_AUTHOR("Xie Xiaolei <xie.xiaolei@stericsson.com>"); +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/codecs/ab3550.h b/sound/soc/codecs/ab3550.h new file mode 100644 index 00000000000..f553fdce96b --- /dev/null +++ b/sound/soc/codecs/ab3550.h @@ -0,0 +1,336 @@ +/* + * 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 + +extern struct snd_soc_dai ab3550_codec_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_ab3550; + +#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/av8100_audio.c b/sound/soc/codecs/av8100_audio.c new file mode 100644 index 00000000000..7bcb985b9eb --- /dev/null +++ b/sound/soc/codecs/av8100_audio.c @@ -0,0 +1,334 @@ +/* + * 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" + +#define AV8100_SUPPORTED_RATE (SNDRV_PCM_RATE_48000) +#define AV8100_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE) + +static int setupAV8100_stereo(void) +{ + union av8100_configuration config; + struct av8100_status status; + int ret; + + pr_debug("%s: Enter.\n", __func__); + + /* Startup AV8100 if it is not already started */ + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + pr_info("%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(NULL, 0, I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Download firmware failed " + "(av8100_download_firmware returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + } + + /* Set the HDMI format of AV8100 */ + pr_info("%s: Setting hdmi_format.", __func__); + config.hdmi_format.hdmi_mode = AV8100_HDMI_ON; + config.hdmi_format.hdmi_format = AV8100_HDMI; + ret = av8100_conf_prep(AV8100_COMMAND_AUDIO_INPUT_FORMAT, &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_AUDIO_INPUT_FORMAT, + NULL, + NULL, + I2C_INTERFACE); + if (ret != 0) { + pr_err("%s: Setting hdmi_format failed " + "(av8100_conf_w returned %d)!\n", + __func__, + ret); + return -EINVAL; + } + + /* Set the audio input format of AV8100 */ + pr_info("%s: Setting audio_input_format.", __func__); + config.audio_input_format.audio_input_if_format = AV8100_AUDIO_I2SDELAYED_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_if_mode = AV8100_AUDIO_MASTER; + 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; + } + if (av8100_conf_w(AV8100_COMMAND_AUDIO_INPUT_FORMAT, + NULL, NULL, I2C_INTERFACE) != 0) { + pr_err("%s: Setting audio_input_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_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + int ret; + int channels; + + pr_debug("%s: Enter.\n", __func__); + + channels = params_channels(hw_params); + switch (channels) { + case 1: + goto error_channels; + case 2: + ret = setupAV8100_stereo(); + break; + case 6: + goto error_channels; + default: + goto error_channels; + } + + return ret; + +error_channels: + pr_err("%s: Unsupported number of channels (%d)!\n", __func__, channels); + + return -1; +} + +static void av8100_codec_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *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) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +struct snd_soc_dai av8100_codec_dai[] = { + { + .name = "av8100_0", + .playback = { + .stream_name = "av8100_0", + .channels_min = 2, + .channels_max = 6, + .rates = AV8100_SUPPORTED_RATE, + .formats = AV8100_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "av8100_0", + .channels_min = 2, + .channels_max = 6, + .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, + .shutdown = av8100_codec_pcm_shutdown, + .set_sysclk = av8100_codec_set_dai_sysclk, + .set_fmt = av8100_codec_set_dai_fmt, + } + }, + } +}; +EXPORT_SYMBOL_GPL(av8100_codec_dai); + +static unsigned int av8100_codec_read(struct snd_soc_codec *codec, + unsigned int ctl) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static int av8100_codec_write(struct snd_soc_codec *codec, + unsigned int ctl, + unsigned int value) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static int av8100_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + pr_info("%s: Enter (pdev = %p).\n", __func__, pdev); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + codec->name = "AV8100"; + codec->owner = THIS_MODULE; + codec->dai = &av8100_codec_dai[0]; + codec->num_dai = 1; + codec->read = av8100_codec_read; + codec->write = av8100_codec_write; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + mutex_init(&codec->mutex); + + socdev->card->codec = codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("%s: Error: to create new PCMs. error %d\n", + __func__, + ret); + goto err; + } + + return 0; + +err: + kfree(codec); + return ret; +} + +static int av8100_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + pr_debug("%s: Enter (pdev = %p).\n", __func__, pdev); + + if (!codec) + return 0; + + snd_soc_free_pcms(socdev); + kfree(socdev->card->codec); + + return 0; +} + +static int av8100_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + pr_debug("%s: Enter (pdev = %p).\n", __func__, pdev); + + return 0; +} + +static int av8100_codec_resume(struct platform_device *pdev) +{ + pr_debug("%s: Enter (pdev = %p).\n", __func__, pdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_av8100 = { + .probe = av8100_codec_probe, + .remove = av8100_codec_remove, + .suspend = av8100_codec_suspend, + .resume = av8100_codec_resume +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_av8100); + +static int __devinit av8100_codec_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + pr_info("%s: Register codec-dai.\n", __func__); + ret = snd_soc_register_dai(&av8100_codec_dai[0]); + if (ret < 0) { + pr_debug("%s: Error: Failed to register codec-dai (ret = %d).\n", + __func__, + ret); + } + + return ret; +} + +static void av8100_codec_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + snd_soc_unregister_dai(&av8100_codec_dai[0]); +} + +module_init(av8100_codec_init); +module_exit(av8100_codec_exit); + +MODULE_DESCRIPTION("AV8100 ASoC codec driver"); +MODULE_AUTHOR("www.stericsson.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/av8100_audio.h b/sound/soc/codecs/av8100_audio.h new file mode 100644 index 00000000000..6d2a7e7a801 --- /dev/null +++ b/sound/soc/codecs/av8100_audio.h @@ -0,0 +1,22 @@ +/* + * 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_CODEC_H +#define AV8100_CODEC_H + +extern struct snd_soc_dai av8100_codec_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_av8100; + +#endif /* AV8100_CODEC_H */ + + + diff --git a/sound/soc/codecs/cg29xx.c b/sound/soc/codecs/cg29xx.c new file mode 100644 index 00000000000..a138f2c211a --- /dev/null +++ b/sound/soc/codecs/cg29xx.c @@ -0,0 +1,778 @@ +/* + * 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. + */ + #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 <linux/mfd/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_codec *cg29xx_codec; + +static struct cg29xx_dai + cg29xx_dai_private[CG29XX_NBR_OF_DAI] = { + { + .tx_active = 0, + .rx_active = 0, + .input_select = 0, + .output_select = 0, + .config = { + .port = PORT_0_I2S, + .conf.i2s.mode = DAI_MODE_SLAVE, + .conf.i2s.half_period = HALF_PER_DUR_16, + .conf.i2s.channel_sel = CHANNEL_SELECTION_BOTH, + .conf.i2s.sample_rate = SAMPLE_RATE_48, + .conf.i2s.word_width = WORD_WIDTH_32 + } + }, + { + .tx_active = 0, + .rx_active = 0, + .input_select = 0, + .output_select = 0, + .config = { + .port = PORT_1_I2S_PCM, + .conf.i2s_pcm.mode = DAI_MODE_SLAVE, + .conf.i2s_pcm.slot_0_dir = DAI_DIR_B_RX_A_TX, + .conf.i2s_pcm.slot_1_dir = DAI_DIR_B_TX_A_RX, + .conf.i2s_pcm.slot_2_dir = DAI_DIR_B_RX_A_TX, + .conf.i2s_pcm.slot_3_dir = DAI_DIR_B_RX_A_TX, + .conf.i2s_pcm.slot_0_used = true, + .conf.i2s_pcm.slot_1_used = false, + .conf.i2s_pcm.slot_2_used = false, + .conf.i2s_pcm.slot_3_used = false, + .conf.i2s_pcm.slot_0_start = 0, + .conf.i2s_pcm.slot_1_start = 16, + .conf.i2s_pcm.slot_2_start = 32, + .conf.i2s_pcm.slot_3_start = 48, + .conf.i2s_pcm.protocol = PORT_PROTOCOL_PCM, + .conf.i2s_pcm.ratio = STREAM_RATIO_FM48_VOICE16, + .conf.i2s_pcm.duration = SYNC_DURATION_32, + .conf.i2s_pcm.clk = BIT_CLK_512, + .conf.i2s_pcm.sample_rate = SAMPLE_RATE_16, + } + }, +}; + +static struct snd_soc_dai_ops cg29xx_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 cg29xx_codec_dai[] = { + { + .name = "cg29xx_0", + .id = 0, + .playback = { + .stream_name = "cg29xx_0_pb", + .channels_min = 2, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE, + .formats = CG29XX_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "cg29xx_0_cap", + .channels_min = 2, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE, + .formats = CG29XX_SUPPORTED_FMT, + }, + .ops = &cg29xx_dai_ops, + .symmetric_rates = 1, + .private_data = &cg29xx_dai_private[0] + }, + { + .name = "cg29xx_1", + .id = 1, + .playback = { + .stream_name = "cg29xx_1_pb", + .channels_min = 1, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE_PCM, + .formats = CG29XX_SUPPORTED_FMT, + }, + .capture = { + .stream_name = "cg29xx_1_cap", + .channels_min = 1, + .channels_max = 2, + .rates = CG29XX_SUPPORTED_RATE_PCM, + .formats = CG29XX_SUPPORTED_FMT, + }, + .ops = &cg29xx_dai_ops, + .symmetric_rates = 1, + .private_data = &cg29xx_dai_private[1] + } +}; +EXPORT_SYMBOL_GPL(cg29xx_codec_dai); + +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 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_dai *private = + codec_dai->private_data; + 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 (private->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) + private->config.conf.i2s.mode = DAI_MODE_MASTER; + else + private->config.conf.i2s.mode = DAI_MODE_SLAVE; + break; + + case SND_SOC_DAIFMT_DSP_A: + if (private->config.port != PORT_1_I2S_PCM || + msel == SND_SOC_DAIFMT_CBM_CFM) { + pr_err("cg29xx_dai: unsupported DAI format 0x%x\n", + fmt); + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int cg29xx_set_tdm_slot( + struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, + int slot_width) +{ + struct cg29xx_dai *private = + dai->private_data; + + if (private->config.port != PORT_1_I2S_PCM) + return -EINVAL; + + private->config.conf.i2s_pcm.slot_0_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT0_SHIFT) ? + true : false; + private->config.conf.i2s_pcm.slot_1_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT1_SHIFT) ? + true : false; + private->config.conf.i2s_pcm.slot_2_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT2_SHIFT) ? + true : false; + private->config.conf.i2s_pcm.slot_3_used = + (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT3_SHIFT) ? + true : false; + + private->config.conf.i2s_pcm.slot_0_start = 0; + private->config.conf.i2s_pcm.slot_1_start = slot_width; + private->config.conf.i2s_pcm.slot_2_start = 2 * slot_width; + private->config.conf.i2s_pcm.slot_3_start = 3 * slot_width; + + return 0; +} + +static int cg29xx_configure_endp( + struct cg29xx_dai *dai, + 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->config.port) { + default: + case PORT_0_I2S: + dai_sr = dai->config.conf.i2s.sample_rate; + break; + + case PORT_1_I2S_PCM: + dai_sr = dai->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_dai *dai, + enum cg29xx_dai_direction direction) +{ + int err = 0; + unsigned int *stream; + + if (direction == CG29XX_DAI_DIRECTION_TX) + stream = &dai->tx_active; + else + stream = &dai->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->config.port); + } + } + + return err; +} + +static int cg29xx_start_if( + struct cg29xx_dai *dai, + 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->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->output_select) { + default: + case 0: + endpid = ENDPOINT_BT_SCO_INOUT; + break; + case 1: + endpid = ENDPOINT_FM_TX; + } + stream = &dai->rx_active; + } else { + switch (dai->input_select) { + default: + case 0: + endpid = ENDPOINT_BT_SCO_INOUT; + break; + case 1: + endpid = ENDPOINT_FM_RX; + } + + stream = &dai->tx_active; + } + + if (*stream) { + 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, 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); + + return err; +} + +static int cg29xx_dai_prepare( + struct snd_pcm_substream *substream, + struct snd_soc_dai *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->private_data, direction); + + return err; +} + +static void cg29xx_dai_shutdown( + struct snd_pcm_substream *substream, + struct snd_soc_dai *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->private_data, direction); +} + +static int cg29xx_dai_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + struct cg29xx_dai *private = dai->private_data; + 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__, + private->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 (private->config.port == PORT_0_I2S) { + private->config.conf.i2s.sample_rate = sr; + } else { + private->config.conf.i2s_pcm.sample_rate = sr; + private->config.conf.i2s_pcm.duration = duration; + private->config.conf.i2s_pcm.clk = bclk; + private->config.conf.i2s_pcm.ratio = ratio; + } + + if (!(private->tx_active | private->rx_active)) { + err = cg2900_audio_set_dai_config( + codec_private.session, + &private->config); + + pr_debug("asoc cg29xx: cg2900_audio_set_dai_config" + "on port %d completed with result: %d.\n", + private->config.port, + err); + } + + return err; +} + +static unsigned int cg29xx_codec_read( + struct snd_soc_codec *codec, + unsigned int reg) +{ + + switch (reg) { + + case INTERFACE0_INPUT_SELECT: + return cg29xx_dai_private[0].input_select; + + case INTERFACE1_INPUT_SELECT: + return cg29xx_dai_private[1].input_select; + + case INTERFACE0_OUTPUT_SELECT: + return cg29xx_dai_private[0].output_select; + + case INTERFACE1_OUTPUT_SELECT: + return cg29xx_dai_private[1].output_select; + + default: + return 0; + } + + return 0; +} + +static int cg29xx_codec_write( + struct snd_soc_codec *codec, + unsigned int reg, + unsigned int value) +{ + int old_value; + struct cg29xx_dai *dai; + enum cg29xx_dai_direction direction; + bool restart_if = false; + + switch (reg) { + + case INTERFACE0_INPUT_SELECT: + dai = &cg29xx_dai_private[0]; + direction = CG29XX_DAI_DIRECTION_TX; + + old_value = dai->input_select; + dai->input_select = value; + + if ((old_value ^ value) && dai->tx_active) + restart_if = true; + break; + + case INTERFACE1_INPUT_SELECT: + dai = &cg29xx_dai_private[1]; + direction = CG29XX_DAI_DIRECTION_TX; + + old_value = dai->input_select; + dai->input_select = value; + + if ((old_value ^ value) && dai->tx_active) + restart_if = true; + break; + + case INTERFACE0_OUTPUT_SELECT: + dai = &cg29xx_dai_private[0]; + direction = CG29XX_DAI_DIRECTION_RX; + + old_value = dai->output_select; + dai->output_select = value; + + if ((old_value ^ value) && dai->rx_active) + restart_if = true; + break; + + case INTERFACE1_OUTPUT_SELECT: + dai = &cg29xx_dai_private[1]; + direction = CG29XX_DAI_DIRECTION_RX; + + old_value = dai->output_select; + dai->output_select = value; + + if ((old_value ^ value) && dai->rx_active) + restart_if = true; + break; + + default: + return -EINVAL; + } + + if (restart_if) { + (void) cg29xx_stop_if(dai, direction); + (void) cg29xx_start_if(dai, direction); + } + + return 0; +} + +static int cg29xx_soc_probe(struct platform_device *pdev) +{ + int err; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + socdev->card->codec = cg29xx_codec; + + err = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (err < 0) { + pr_err("cg29xx asoc - snd_soc_new_pcms failed with error: %d\n", + err); + goto err1; + } + + snd_soc_add_controls( + cg29xx_codec, + cg29xx_snd_controls, + ARRAY_SIZE(cg29xx_snd_controls)); + + return 0; + + snd_soc_free_pcms(socdev); +err1: + return err; +} + +static int cg29xx_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + + return 0; +} + +static int cg29xx_soc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int cg29xx_soc_resume(struct platform_device *pdev) +{ + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_cg29xx = { + .probe = cg29xx_soc_probe, + .remove = cg29xx_soc_remove, + .suspend = cg29xx_soc_suspend, + .resume = cg29xx_soc_resume +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_cg29xx); + +static int __init cg29xx_init(void) +{ + int err; + int i; + + cg29xx_codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + + if (!cg29xx_codec) + return -ENOMEM; + + cg29xx_codec->name = "CG29XX"; + cg29xx_codec->owner = THIS_MODULE; + cg29xx_codec->dai = cg29xx_codec_dai; + cg29xx_codec->num_dai = CG29XX_NBR_OF_DAI; + cg29xx_codec->read = cg29xx_codec_read; + cg29xx_codec->write = cg29xx_codec_write; + INIT_LIST_HEAD(&cg29xx_codec->dapm_widgets); + INIT_LIST_HEAD(&cg29xx_codec->dapm_paths); + mutex_init(&cg29xx_codec->mutex); + + err = snd_soc_register_codec(cg29xx_codec); + + if (err) { + pr_err( + "asoc cg29xx - snd_soc_register_codec" + " failed with error: %d.\n", + err); + + return err; + } + + for (i = 0; i < CG29XX_NBR_OF_DAI; i++) { + mutex_init(&cg29xx_dai_private[i].mutex); + + err = snd_soc_register_dai(&cg29xx_codec_dai[i]); + + if (err) { + pr_err( + "asoc cg29xx - snd_soc_register_dai" + " failed with error: %d.\n", + err); + return err; + } + } + + return err; +} +module_init(cg29xx_init); + +static void __exit cg29xx_exit(void) +{ + int i; + + (void) cg2900_audio_close(&codec_private.session); + + if (cg29xx_codec) { + snd_soc_unregister_codec(cg29xx_codec); + kfree(cg29xx_codec); + cg29xx_codec = NULL; + } + + for (i = 0; i < CG29XX_NBR_OF_DAI; i++) + snd_soc_unregister_dai(&cg29xx_codec_dai[i]); +} +module_exit(cg29xx_exit); + +MODULE_DESCRIPTION("CG29xx codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/cg29xx.h b/sound/soc/codecs/cg29xx.h new file mode 100644 index 00000000000..e7ced7f9e24 --- /dev/null +++ b/sound/soc/codecs/cg29xx.h @@ -0,0 +1,44 @@ +/* + * 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 <linux/mfd/cg2900_audio.h> + +extern struct snd_soc_dai cg29xx_codec_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_cg29xx; + +struct cg29xx_dai { + 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 */ |