summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorKristoffer KARLSSON <kristoffer.karlsson@stericsson.com>2011-10-10 10:39:07 +0200
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-05-22 11:04:59 +0200
commit31f8e7bdf85a77e46dc64fafcc6aaf58ecd68883 (patch)
treeae5c693db6958842c3cf3d5e5e1a4f0af74ded1b /sound
parentde67f4c0cf87d7e083745ef8809a64439d2920d7 (diff)
Ux500 ASoC: Add ANC functionality
This patch adds support for control of the ANC block in the ab8500 chipset. ST-Ericsson Linux next: NA ST-Ericsson ID: 325656 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Icf57820d32b218f16f13d96d195b2cba6b31f494 Signed-off-by: Kristoffer KARLSSON <kristoffer.karlsson@stericsson.com> Change-Id: Ic841ecb0ae13939c84e8d959ab3cb74986e3b72b Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32988 Reviewed-by: Roger NILSSON1 <roger.xr.nilsson@stericsson.com> Reviewed-by: QATOOLS
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/ab8500_audio.c166
-rw-r--r--sound/soc/codecs/ab8500_audio.h25
-rw-r--r--sound/soc/ux500/ux500_ab8500.c56
3 files changed, 241 insertions, 6 deletions
diff --git a/sound/soc/codecs/ab8500_audio.c b/sound/soc/codecs/ab8500_audio.c
index ad2fb60c6ab..8d719e7950a 100644
--- a/sound/soc/codecs/ab8500_audio.c
+++ b/sound/soc/codecs/ab8500_audio.c
@@ -55,6 +55,23 @@
#define AB8500_GPIO_DIR5_REG 0x1014
#define AB8500_GPIO_OUT5_REG 0x1024
+/* Nr of FIR/IIR-coeff banks in ANC-block */
+#define AB8500_NR_OF_ANC_COEFF_BANKS 2
+
+/* Macros to simplify implementation of register write sequences and error handling */
+#define AB8500_SET_BIT_LOCKED(xreg, xbit, xerr, xerr_hdl) { \
+ xerr = snd_soc_update_bits_locked(ab8500_codec, xreg, REG_MASK_NONE, BMASK(xbit)); \
+ if (xerr < 0) \
+ goto xerr_hdl; }
+#define AB8500_CLEAR_BIT_LOCKED(xreg, xbit, xerr, xerr_hdl) { \
+ xerr = snd_soc_update_bits_locked(ab8500_codec, xreg, BMASK(xbit), REG_MASK_NONE); \
+ if (xerr < 0) \
+ goto xerr_hdl; }
+#define AB8500_WRITE(xreg, xvalue, xerr, xerr_hdl) { \
+ xerr = snd_soc_write(ab8500_codec, xreg, xvalue); \
+ if (xerr < 0) \
+ goto xerr_hdl; }
+
/*
* AB8500 register cache & default register settings
*/
@@ -184,6 +201,26 @@ struct soc_smra_control {
long *values;
};
+/* ANC FIR- & IIR-coeff caches */
+static int ab8500_anc_fir_coeff_cache[REG_ANC_FIR_COEFFS];
+static int ab8500_anc_iir_coeff_cache[REG_ANC_IIR_COEFFS];
+
+/* ANC states */
+enum anc_states {
+ ANC_UNCONFIGURED = 0,
+ ANC_CONFIGURE_FIR_IIR = 1,
+ ANC_FIR_IIR_CONFIGURED = 2,
+ ANC_CONFIGURE_FIR = 3,
+ ANC_FIR_CONFIGURED = 4,
+ ANC_CONFIGURE_IIR = 5,
+ ANC_IIR_CONFIGURED = 6,
+ ANC_ERROR = 7
+};
+static int ab8500_anc_status = ANC_UNCONFIGURED;
+
+/* ANC configuration lock */
+static DEFINE_MUTEX(ab8500_anc_conf_lock);
+
/* Reads an arbitrary register from the ab8500 chip.
*/
static int ab8500_codec_read_reg(struct snd_soc_codec *codec, unsigned int bank,
@@ -1933,6 +1970,38 @@ static struct snd_kcontrol_new ab8500_snd_controls[] = {
REG_SIDFIRADR_ADDRESS_SHIFT,
REG_SIDFIRADR_ADDRESS_MAX,
NORMAL),
+
+ /* ANC */
+ SOC_SINGLE_S1R("ANC Warp Delay Shift",
+ REG_ANCCONF2,
+ REG_ANCCONF2_VALUE_MIN,
+ REG_ANCCONF2_VALUE_MAX,
+ NORMAL),
+ SOC_SINGLE_S1R("ANC FIR Output Shift",
+ REG_ANCCONF3,
+ REG_ANCCONF3_VALUE_MIN,
+ REG_ANCCONF3_VALUE_MAX,
+ NORMAL),
+ SOC_SINGLE_S1R("ANC IIR Output Shift",
+ REG_ANCCONF4,
+ REG_ANCCONF4_VALUE_MIN,
+ REG_ANCCONF4_VALUE_MAX,
+ NORMAL),
+ SOC_SINGLE_S2R("ANC Warp Delay",
+ REG_ANCCONF9, REG_ANCCONF10,
+ REG_ANC_WARP_DELAY_MIN,
+ REG_ANC_WARP_DELAY_MAX,
+ NORMAL),
+ SOC_MULTIPLE_SA("ANC FIR Coefficients",
+ ab8500_anc_fir_coeff_cache,
+ REG_ANC_FIR_COEFF_MIN,
+ REG_ANC_FIR_COEFF_MAX,
+ NORMAL),
+ SOC_MULTIPLE_SA("ANC IIR Coefficients",
+ ab8500_anc_iir_coeff_cache,
+ REG_ANC_IIR_COEFF_MIN,
+ REG_ANC_IIR_COEFF_MAX,
+ NORMAL),
};
static int ab8500_codec_set_format_if1(struct snd_soc_codec *codec, unsigned int fmt)
@@ -2236,6 +2305,103 @@ int ab8500_audio_setup_if1(struct snd_soc_codec *codec,
return 0;
}
+/* ANC block current configuration status */
+unsigned int ab8500_audio_anc_status(void)
+{
+ return ab8500_anc_status;
+}
+
+/* ANC IIR-/FIR-coefficients configuration sequence */
+int ab8500_audio_anc_configure(unsigned int req_state)
+{
+ bool configure_fir = req_state == ANC_CONFIGURE_FIR || req_state == ANC_CONFIGURE_FIR_IIR;
+ bool configure_iir = req_state == ANC_CONFIGURE_IIR || req_state == ANC_CONFIGURE_FIR_IIR;
+ unsigned int bank, param;
+ int ret;
+
+ if (req_state == ANC_UNCONFIGURED
+ || req_state == ANC_FIR_IIR_CONFIGURED
+ || req_state == ANC_FIR_CONFIGURED
+ || req_state == ANC_IIR_CONFIGURED
+ || req_state == ANC_ERROR)
+ return -1;
+
+ mutex_lock(&ab8500_anc_conf_lock);
+
+ if (configure_fir)
+ AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ENANC, ret, cleanup)
+
+ AB8500_SET_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ENANC, ret, cleanup)
+
+ if (configure_fir) {
+ for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) {
+ for (param = 0; param < REG_ANC_FIR_COEFFS; param++) {
+ if (param == 0 && bank == 0)
+ AB8500_SET_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCFIRUPDATE, ret, cleanup)
+
+ AB8500_WRITE(REG_ANCCONF5, ab8500_anc_fir_coeff_cache[param] >> 8 & REG_MASK_ALL, ret, cleanup)
+ AB8500_WRITE(REG_ANCCONF6, ab8500_anc_fir_coeff_cache[param] & REG_MASK_ALL, ret, cleanup)
+
+ if (param == REG_ANC_FIR_COEFFS - 1 && bank == 1)
+ AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCFIRUPDATE, ret, cleanup)
+ }
+ }
+ if (ab8500_anc_status == ANC_IIR_CONFIGURED)
+ ab8500_anc_status = ANC_FIR_IIR_CONFIGURED;
+ else if (ab8500_anc_status != ANC_FIR_IIR_CONFIGURED)
+ ab8500_anc_status = ANC_FIR_CONFIGURED;
+ }
+
+ if (configure_iir) {
+ for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) {
+ for (param = 0; param < REG_ANC_IIR_COEFFS; param++) {
+ if (param == 0) {
+ if (bank == 0) {
+ AB8500_SET_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCIIRINIT, ret, cleanup)
+ udelay(2000);
+ AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCIIRINIT, ret, cleanup)
+ udelay(2000);
+ } else {
+ AB8500_SET_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCIIRUPDATE, ret, cleanup)
+ }
+ } else if (param > 3) {
+ AB8500_WRITE(REG_ANCCONF7, REG_MASK_NONE, ret, cleanup)
+ AB8500_WRITE(REG_ANCCONF8, ab8500_anc_iir_coeff_cache[param] >> 16 & REG_MASK_ALL, ret, cleanup)
+ }
+
+ AB8500_WRITE(REG_ANCCONF7, ab8500_anc_iir_coeff_cache[param] >> 8 & REG_MASK_ALL, ret, cleanup)
+ AB8500_WRITE(REG_ANCCONF8, ab8500_anc_iir_coeff_cache[param] & REG_MASK_ALL, ret, cleanup)
+
+ if (param == REG_ANC_IIR_COEFFS - 1 && bank == 1)
+ AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ANCIIRUPDATE, ret, cleanup)
+ }
+ }
+ if (ab8500_anc_status == ANC_FIR_CONFIGURED)
+ ab8500_anc_status = ANC_FIR_IIR_CONFIGURED;
+ else if (ab8500_anc_status != ANC_FIR_IIR_CONFIGURED)
+ ab8500_anc_status = ANC_IIR_CONFIGURED;
+ }
+
+ mutex_unlock(&ab8500_anc_conf_lock);
+
+ return 0;
+
+cleanup:
+ ret |= snd_soc_update_bits_locked(ab8500_codec
+ , REG_ANCCONF1
+ , BMASK(REG_ANCCONF1_ENANC)
+ | BMASK(REG_ANCCONF1_ANCIIRINIT)
+ | BMASK(REG_ANCCONF1_ANCIIRUPDATE)
+ | BMASK(REG_ANCCONF1_ANCFIRUPDATE)
+ , REG_MASK_NONE);
+
+ ab8500_anc_status = ANC_ERROR;
+
+ mutex_unlock(&ab8500_anc_conf_lock);
+
+ return ret;
+}
+
bool ab8500_audio_dapm_path_active(enum ab8500_audio_dapm_path dapm_path)
{
int reg, reg_mask;
diff --git a/sound/soc/codecs/ab8500_audio.h b/sound/soc/codecs/ab8500_audio.h
index 2d659c58746..2b2f69e44c2 100644
--- a/sound/soc/codecs/ab8500_audio.h
+++ b/sound/soc/codecs/ab8500_audio.h
@@ -31,6 +31,8 @@ int ab8500_audio_setup_if1(struct snd_soc_codec *codec,
unsigned int fmt,
unsigned int wl,
unsigned int delay);
+unsigned int ab8500_audio_anc_status();
+int ab8500_audio_anc_configure(unsigned int req_state);
enum ab8500_audio_dapm_path {
AB8500_AUDIO_DAPM_PATH_DMIC,
@@ -588,14 +590,25 @@ bool ab8500_audio_dapm_path_active(enum ab8500_audio_dapm_path dapm_path);
#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 */
-/* REG_ANCCONF5 */
-/* REG_ANCCONF6 */
-/* REG_ANCCONF7 */
-/* REG_ANCCONF8 */
-/* REG_ANCCONF9 */
-/* REG_ANCCONF10 */
+#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 */
diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c
index d45436a8a03..34c6aa0eef4 100644
--- a/sound/soc/ux500/ux500_ab8500.c
+++ b/sound/soc/ux500/ux500_ab8500.c
@@ -59,6 +59,19 @@ static struct clk *clk_ptr_sysclk;
static struct clk *clk_ptr_ulpclk;
static struct clk *clk_ptr_gpio1;
+/* ANC States */
+static const char *enum_anc_state[] = {
+ "Unconfigured",
+ "Configure FIR+IIR",
+ "FIR+IIR Configured",
+ "Configure FIR",
+ "FIR Configured",
+ "Configure IIR",
+ "IIR Configured",
+ "Error"
+};
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state);
+
/* Regulators */
enum regulator_idx {
REGULATOR_AUDIO,
@@ -285,6 +298,48 @@ static const struct snd_kcontrol_new mclk_input_control = {
.private_value = 1 /* ULPCLK */
};
+static int anc_status_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = ab8500_audio_anc_status();
+
+ return 0;
+}
+
+static int anc_status_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ int req_state = ucontrol->value.integer.value[0];
+
+ ret = ux500_ab8500_power_control_inc();
+ if (ret)
+ goto cleanup;
+
+ ret = ab8500_audio_anc_configure(req_state);
+
+ ux500_ab8500_power_control_dec();
+
+cleanup:
+ if (ret) {
+ pr_err("%s: Unable to configure ANC! (ret = %d)\n",
+ __func__, ret);
+ return 0;
+ }
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new anc_status_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ANC Status",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_soc_info_enum_ext,
+ .get = anc_status_control_get, .put = anc_status_control_put,
+ .private_value = (unsigned long) &soc_enum_ancstate
+};
+
/* DAPM-events */
static int dapm_audioreg_event(struct snd_soc_dapm_widget *w,
@@ -573,6 +628,7 @@ int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd)
/* 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));
/* Get references to clock-nodes */
clk_ptr_sysclk = NULL;