summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMikko Sarmanne <mikko.sarmanne@symbio.com>2010-12-15 08:43:08 +0200
committerPhilippe Langlais <philippe.langlais@stericsson.com>2011-12-06 10:59:28 +0100
commit968b4a8ca3664ee4d2032fa30ba309e7d81e2717 (patch)
tree5ab1ba69705b0da3cb552dc649f2d33273955a12 /sound
parent073384cf5d5f49e9b5e0c64ff7b6fd1f71702ddc (diff)
ASoC: Add support for AB8500 audio
Adds ALSA SoC support for AB8500 audio codec for 2.6.35 kernel. Signed-off-by: Mikko Sarmanne <mikko.sarmanne@symbio.com> Change-Id: Ibb8c0ae51ebd7c31615fd214292f3be2063a3dcf Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/12277 Reviewed-by: Ola LILJA2 <ola.o.lilja@stericsson.com> Tested-by: Ola LILJA2 <ola.o.lilja@stericsson.com> Reviewed-by: QATOOLS Reviewed-by: Linus WALLEIJ <linus.walleij@stericsson.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/Kconfig4
-rw-r--r--sound/soc/codecs/Makefile3
-rw-r--r--sound/soc/codecs/ab8500.c1714
-rw-r--r--sound/soc/codecs/ab8500.h484
-rw-r--r--sound/soc/ux500/Kconfig8
-rw-r--r--sound/soc/ux500/Makefile8
-rw-r--r--sound/soc/ux500/u8500.c43
-rw-r--r--sound/soc/ux500/ux500_ab8500.c275
-rw-r--r--sound/soc/ux500/ux500_ab8500.h34
-rw-r--r--sound/soc/ux500/ux500_ab8500_accessory.c1349
-rw-r--r--sound/soc/ux500/ux500_ab8500_accessory.h22
11 files changed, 3943 insertions, 1 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index e1bb59c8ef4..b4d7c014eb4 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -102,6 +102,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
select SND_SOC_AB3550
select SND_SOC_AB5500
+ select SND_SOC_AB8500
select SND_SOC_CG29XX
select SND_SOC_AV8100
help
@@ -402,6 +403,9 @@ config SND_SOC_AB3550
config SND_SOC_AB5500
tristate
+config SND_SOC_AB8500
+ tristate
+
config SND_SOC_CG29XX
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 8d8135969fd..936810e24dd 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -1,6 +1,7 @@
snd-soc-88pm860x-objs := 88pm860x-codec.o
snd-soc-ab3550-objs := ab3550.o
snd-soc-ab5500-objs := ab5500.o
+snd-soc-ab8500-objs := ab8500.o
snd-soc-ac97-objs := ac97.o
snd-soc-ad1836-objs := ad1836.o
snd-soc-ad193x-objs := ad193x.o
@@ -100,6 +101,7 @@ snd-soc-wm9090-objs := wm9090.o
obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-ab3550.o
obj-$(CONFIG_SND_SOC_AB5500) += snd-soc-ab5500.o
+obj-$(CONFIG_SND_SOC_AB8500) += snd-soc-ab8500.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
@@ -201,4 +203,5 @@ ifdef CONFIG_SND_SOC_UX500_DEBUG
CFLAGS_av8100_audio.o := -DDEBUG
CFLAGS_ab3550.o := -DDEBUG
CFLAGS_cg29xx.o := -DDEBUG
+CFLAGS_ab8500.o := -DDEBUG
endif
diff --git a/sound/soc/codecs/ab8500.c b/sound/soc/codecs/ab8500.c
new file mode 100644
index 00000000000..f9ae047fe99
--- /dev/null
+++ b/sound/soc/codecs/ab8500.c
@@ -0,0 +1,1714 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>,
+ * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <linux/mfd/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include "ab8500.h"
+
+/* To convert register definition shifts to masks */
+#define BMASK(bsft) (1 << (bsft))
+
+/* Macrocell value definitions */
+#define CLK_32K_OUT2_DISABLE 0x01
+#define INACTIVE_RESET_AUDIO 0x02
+#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10
+#define ENABLE_VINTCORE12_SUPPLY 0x04
+#define GPIO27_DIR_OUTPUT 0x04
+#define GPIO29_DIR_OUTPUT 0x10
+#define GPIO31_DIR_OUTPUT 0x40
+
+/* Macrocell register definitions */
+#define AB8500_CTRL3_REG 0x0200
+#define AB8500_SYSULPCLK_CTRL1_REG 0x020B
+#define AB8500_GPIO_DIR4_REG 0x1013
+
+/*
+ * AB8500 register cache & default register settings
+ */
+static const u8 ab8500_reg_cache[AB8500_CACHEREGNUM] = {
+ 0x88, /* REG_POWERUP (0x00) */
+ 0x00, /* REG_AUDSWRESET (0x01) */
+ 0x00, /* REG_ADPATHENA (0x02) */
+ 0x00, /* REG_DAPATHENA (0x03) */
+ 0x00, /* REG_ANACONF1 (0x04) */
+ 0x0F, /* REG_ANACONF2 (0x05) */
+ 0x00, /* REG_DIGMICCONF (0x06) */
+ 0x00, /* REG_ANACONF3 (0x07) */
+ 0x00, /* REG_ANACONF4 (0x08) */
+ 0x00, /* REG_DAPATHCONF (0x09) */
+ 0x40, /* REG_MUTECONF (0x0A) */
+ 0x00, /* REG_SHORTCIRCONF (0x0B) */
+ 0x01, /* REG_ANACONF5 (0x0C) */
+ 0x00, /* REG_ENVCPCONF (0x0D) */
+ 0x00, /* REG_SIGENVCONF (0x0E) */
+ 0x3F, /* REG_PWMGENCONF1 (0x0F) */
+ 0x32, /* REG_PWMGENCONF2 (0x10) */
+ 0x32, /* REG_PWMGENCONF3 (0x11) */
+ 0x32, /* REG_PWMGENCONF4 (0x12) */
+ 0x32, /* REG_PWMGENCONF5 (0x13) */
+ 0x0F, /* REG_ANAGAIN1 (0x14) */
+ 0x0F, /* REG_ANAGAIN2 (0x15) */
+ 0x22, /* REG_ANAGAIN3 (0x16) */
+ 0x55, /* REG_ANAGAIN4 (0x17) */
+ 0x13, /* REG_DIGLINHSLGAIN (0x18) */
+ 0x13, /* REG_DIGLINHSRGAIN (0x19) */
+ 0x00, /* REG_ADFILTCONF (0x1A) */
+ 0x00, /* REG_DIGIFCONF1 (0x1B) */
+ 0x02, /* REG_DIGIFCONF2 (0x1C) */
+ 0x00, /* REG_DIGIFCONF3 (0x1D) */
+ 0x02, /* REG_DIGIFCONF4 (0x1E) */
+ 0xCC, /* REG_ADSLOTSEL1 (0x1F) */
+ 0xCC, /* REG_ADSLOTSEL2 (0x20) */
+ 0xCC, /* REG_ADSLOTSEL3 (0x21) */
+ 0xCC, /* REG_ADSLOTSEL4 (0x22) */
+ 0xCC, /* REG_ADSLOTSEL5 (0x23) */
+ 0xCC, /* REG_ADSLOTSEL6 (0x24) */
+ 0xCC, /* REG_ADSLOTSEL7 (0x25) */
+ 0xCC, /* REG_ADSLOTSEL8 (0x26) */
+ 0xCC, /* REG_ADSLOTSEL9 (0x27) */
+ 0xCC, /* REG_ADSLOTSEL10 (0x28) */
+ 0xCC, /* REG_ADSLOTSEL11 (0x29) */
+ 0xCC, /* REG_ADSLOTSEL12 (0x2A) */
+ 0xCC, /* REG_ADSLOTSEL13 (0x2B) */
+ 0xCC, /* REG_ADSLOTSEL14 (0x2C) */
+ 0xCC, /* REG_ADSLOTSEL15 (0x2D) */
+ 0xCC, /* REG_ADSLOTSEL16 (0x2E) */
+ 0x00, /* REG_ADSLOTHIZCTRL1 (0x2F) */
+ 0x00, /* REG_ADSLOTHIZCTRL2 (0x30) */
+ 0x00, /* REG_ADSLOTHIZCTRL3 (0x31) */
+ 0x00, /* REG_ADSLOTHIZCTRL4 (0x32) */
+ 0x08, /* REG_DASLOTCONF1 (0x33) */
+ 0x08, /* REG_DASLOTCONF2 (0x34) */
+ 0x08, /* REG_DASLOTCONF3 (0x35) */
+ 0x08, /* REG_DASLOTCONF4 (0x36) */
+ 0x08, /* REG_DASLOTCONF5 (0x37) */
+ 0x08, /* REG_DASLOTCONF6 (0x38) */
+ 0x08, /* REG_DASLOTCONF7 (0x39) */
+ 0x08, /* REG_DASLOTCONF8 (0x3A) */
+ 0x00, /* REG_CLASSDCONF1 (0x3B) */
+ 0x00, /* REG_CLASSDCONF2 (0x3C) */
+ 0x84, /* REG_CLASSDCONF3 (0x3D) */
+ 0x00, /* REG_DMICFILTCONF (0x3E) */
+ 0xFE, /* REG_DIGMULTCONF1 (0x3F) */
+ 0xC0, /* REG_DIGMULTCONF2 (0x40) */
+ 0x3F, /* REG_ADDIGGAIN1 (0x41) */
+ 0x3F, /* REG_ADDIGGAIN2 (0x42) */
+ 0x1F, /* REG_ADDIGGAIN3 (0x43) */
+ 0x1F, /* REG_ADDIGGAIN4 (0x44) */
+ 0x3F, /* REG_ADDIGGAIN5 (0x45) */
+ 0x3F, /* REG_ADDIGGAIN6 (0x46) */
+ 0x1F, /* REG_DADIGGAIN1 (0x47) */
+ 0x1F, /* REG_DADIGGAIN2 (0x48) */
+ 0x3F, /* REG_DADIGGAIN3 (0x49) */
+ 0x3F, /* REG_DADIGGAIN4 (0x4A) */
+ 0x3F, /* REG_DADIGGAIN5 (0x4B) */
+ 0x3F, /* REG_DADIGGAIN6 (0x4C) */
+ 0x3F, /* REG_ADDIGLOOPGAIN1 (0x4D) */
+ 0x3F, /* REG_ADDIGLOOPGAIN2 (0x4E) */
+ 0x00, /* REG_HSLEARDIGGAIN (0x4F) */
+ 0x00, /* REG_HSRDIGGAIN (0x50) */
+ 0x1F, /* REG_SIDFIRGAIN1 (0x51) */
+ 0x1F, /* REG_SIDFIRGAIN2 (0x52) */
+ 0x00, /* REG_ANCCONF1 (0x53) */
+ 0x00, /* REG_ANCCONF2 (0x54) */
+ 0x00, /* REG_ANCCONF3 (0x55) */
+ 0x00, /* REG_ANCCONF4 (0x56) */
+ 0x00, /* REG_ANCCONF5 (0x57) */
+ 0x00, /* REG_ANCCONF6 (0x58) */
+ 0x00, /* REG_ANCCONF7 (0x59) */
+ 0x00, /* REG_ANCCONF8 (0x5A) */
+ 0x00, /* REG_ANCCONF9 (0x5B) */
+ 0x00, /* REG_ANCCONF10 (0x5C) */
+ 0x00, /* REG_ANCCONF11 (0x5D) - read only */
+ 0x00, /* REG_ANCCONF12 (0x5E) - read only */
+ 0x00, /* REG_ANCCONF13 (0x5F) - read only */
+ 0x00, /* REG_ANCCONF14 (0x60) - read only */
+ 0x00, /* REG_SIDFIRADR (0x61) */
+ 0x00, /* REG_SIDFIRCOEF1 (0x62) */
+ 0x00, /* REG_SIDFIRCOEF2 (0x63) */
+ 0x00, /* REG_SIDFIRCONF (0x64) */
+ 0x00, /* REG_AUDINTMASK1 (0x65) */
+ 0x00, /* REG_AUDINTSOURCE1 (0x66) - read only */
+ 0x00, /* REG_AUDINTMASK2 (0x67) */
+ 0x00, /* REG_AUDINTSOURCE2 (0x68) - read only */
+ 0x00, /* REG_FIFOCONF1 (0x69) */
+ 0x00, /* REG_FIFOCONF2 (0x6A) */
+ 0x00, /* REG_FIFOCONF3 (0x6B) */
+ 0x00, /* REG_FIFOCONF4 (0x6C) */
+ 0x00, /* REG_FIFOCONF5 (0x6D) */
+ 0x00, /* REG_FIFOCONF6 (0x6E) */
+ 0x02, /* REG_AUDREV (0x6F) - read only */
+};
+
+/* Reads an arbitrary register from the ab8500 chip.
+*/
+static int ab8500_read_reg(struct snd_soc_codec *codec, unsigned int bank,
+ unsigned int reg)
+{
+ u8 value;
+ int status = abx500_get_register_interruptible(
+ codec->dev, bank, reg, &value);
+
+ if (status < 0) {
+ pr_err("%s: Register (%02x:%02x) read failed (%d).\n",
+ __func__, (u8)bank, (u8)reg, status);
+ } else {
+ pr_debug("Read 0x%02x from register %02x:%02x\n",
+ (u8)value, (u8)bank, (u8)reg);
+ status = value;
+ }
+
+ return status;
+}
+
+/* Writes an arbitrary register to the ab8500 chip.
+ */
+static int ab8500_write_reg(struct snd_soc_codec *codec, unsigned int bank,
+ unsigned int reg, unsigned int value)
+{
+ int status = abx500_set_register_interruptible(
+ codec->dev, bank, reg, value);
+
+ if (status < 0) {
+ pr_err("%s: Register (%02x:%02x) write failed (%d).\n",
+ __func__, (u8)bank, (u8)reg, status);
+ } else {
+ pr_debug("Wrote 0x%02x into register %02x:%02x\n",
+ (u8)value, (u8)bank, (u8)reg);
+ }
+
+ return status;
+}
+
+/* Reads an audio register from the cache.
+ */
+static unsigned int ab8500_audio_read_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+ return cache[reg];
+}
+
+/* Reads an audio register from the hardware.
+ */
+static int ab8500_audio_read_reg_nocache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+ int value = ab8500_read_reg(codec, AB8500_AUDIO, reg);
+
+ if (value >= 0)
+ cache[reg] = value;
+
+ return value;
+}
+
+/* Writes an audio register to the hardware and cache.
+ */
+static int ab8500_audio_write_reg(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+ int status = ab8500_write_reg(codec, AB8500_AUDIO, reg, value);
+
+ if (status >= 0)
+ cache[reg] = value;
+
+ return status;
+}
+
+/* Dumps all audio registers.
+ */
+static inline void ab8500_audio_dump_all_reg(struct snd_soc_codec *codec)
+{
+ int i;
+
+ pr_debug("%s Enter.\n", __func__);
+
+ for (i = AB8500_FIRST_REG; i <= AB8500_LAST_REG; i++)
+ ab8500_audio_read_reg_nocache(codec, i);
+}
+
+/* Updates an audio register.
+ */
+static inline int ab8500_update_audio_reg(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int clr, unsigned int ins)
+{
+ unsigned int new, old;
+
+ old = ab8500_audio_read_reg(codec, reg);
+ new = (old & ~clr) | ins;
+ if (old == new)
+ return 0;
+
+ return ab8500_audio_write_reg(codec, reg, new);
+}
+
+/*--------------------------------------------------------------*/
+
+/* Whether widget's register definitions should be inverted or not */
+enum control_inversion {
+ NORMAL = 0,
+ INVERT = 1
+};
+
+/* HS left channel mute control */
+static const struct snd_kcontrol_new dapm_hsl_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_MUTECONF,
+ REG_MUTECONF_MUTHSL, 1, INVERT),
+};
+
+/* HS right channel mute control */
+static const struct snd_kcontrol_new dapm_hsr_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_MUTECONF,
+ REG_MUTECONF_MUTHSR, 1, INVERT),
+};
+
+/* Earpiece mute control */
+static const struct snd_kcontrol_new dapm_ear_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_MUTECONF,
+ REG_MUTECONF_MUTEAR, 1, INVERT),
+};
+
+/* IHF left channel mute control */
+static const struct snd_kcontrol_new dapm_ihfl_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_DATOHFLEN, 1, NORMAL),
+};
+
+/* IHF right channel mute control */
+static const struct snd_kcontrol_new dapm_ihfr_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_DATOHFREN, 1, NORMAL),
+};
+
+/* Mic 1 mute control */
+static const struct snd_kcontrol_new dapm_mic1_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2,
+ REG_ANACONF2_MUTMIC1, 1, INVERT),
+};
+
+/* Mic 2 mute control */
+static const struct snd_kcontrol_new dapm_mic2_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2,
+ REG_ANACONF2_MUTMIC2, 1, INVERT),
+};
+
+/* LineIn left channel mute control */
+static const struct snd_kcontrol_new dapm_linl_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2,
+ REG_ANACONF2_MUTLINL, 1, INVERT),
+};
+
+/* LineIn right channel mute control */
+static const struct snd_kcontrol_new dapm_linr_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2,
+ REG_ANACONF2_MUTLINR, 1, INVERT),
+};
+
+/* DMic 1 mute control */
+static const struct snd_kcontrol_new dapm_dmic1_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC1, 1, NORMAL),
+};
+
+/* DMic 2 mute control */
+static const struct snd_kcontrol_new dapm_dmic2_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC2, 1, NORMAL),
+};
+
+/* DMic 3 mute control */
+static const struct snd_kcontrol_new dapm_dmic3_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC3, 1, NORMAL),
+};
+
+/* DMic 4 mute control */
+static const struct snd_kcontrol_new dapm_dmic4_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC4, 1, NORMAL),
+};
+
+/* DMic 5 mute control */
+static const struct snd_kcontrol_new dapm_dmic5_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC5, 1, NORMAL),
+};
+
+/* DMic 6 mute control */
+static const struct snd_kcontrol_new dapm_dmic6_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC6, 1, NORMAL),
+};
+
+/* ANC to Earpiece mute control */
+static const struct snd_kcontrol_new dapm_anc_ear_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_ANCSEL, 1, NORMAL),
+};
+
+/* Handsfree left channel selector control */
+static const char *enum_ihfl_sel[] = {"DA 3", "ANC"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfl_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_HFLSEL, enum_ihfl_sel);
+
+static const struct snd_kcontrol_new dapm_ihfl_select[] = {
+ SOC_DAPM_ENUM("Handsfree Left Select", dapm_enum_ihfl_sel),
+};
+
+/* Handsfree right channel selector control */
+static const char *enum_ihfr_sel[] = {"DA 4", "ANC"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfr_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_HFRSEL, enum_ihfr_sel);
+
+static const struct snd_kcontrol_new dapm_ihfr_select[] = {
+ SOC_DAPM_ENUM("Handsfree Right Select", dapm_enum_ihfr_sel),
+};
+
+/* Mic 1A/1B selector control */
+static const char *enum_mic1ab_sel[] = {"Mic 1A", "Mic 1B"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_mic1ab_sel, REG_ANACONF3,
+ REG_ANACONF3_MIC1SEL, enum_mic1ab_sel);
+
+static const struct snd_kcontrol_new dapm_mic1ab_select[] = {
+ SOC_DAPM_ENUM("Mic 1A/1B Select", dapm_enum_mic1ab_sel),
+};
+
+/* Mic 2 / LineIn Right selector control */
+static const char *enum_mic2lr_sel[] = {"Mic 2", "LineIn Right"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_mic2lr_sel, REG_ANACONF3,
+ REG_ANACONF3_LINRSEL, enum_mic2lr_sel);
+
+static const struct snd_kcontrol_new dapm_mic2lr_select[] = {
+ SOC_DAPM_ENUM("Mic 2 / LINR Select", dapm_enum_mic2lr_sel),
+};
+
+/* AD1 selector control */
+static const char *enum_ad1_sel[] = {"LineIn Left", "DMic 1"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad1_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD1SEL, enum_ad1_sel);
+
+static const struct snd_kcontrol_new dapm_ad1_select[] = {
+ SOC_DAPM_ENUM("AD 1 Select", dapm_enum_ad1_sel),
+};
+
+/* AD2 selector control */
+static const char *enum_ad2_sel[] = {"LineIn Right", "DMic 2"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad2_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD2SEL, enum_ad2_sel);
+
+static const struct snd_kcontrol_new dapm_ad2_select[] = {
+ SOC_DAPM_ENUM("AD 2 Select", dapm_enum_ad2_sel),
+};
+
+/* AD3 selector control */
+static const char *enum_ad3_sel[] = {"Mic 1A/1B", "DMic 3"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad3_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD3SEL, enum_ad3_sel);
+
+static const struct snd_kcontrol_new dapm_ad3_select[] = {
+ SOC_DAPM_ENUM("AD 3 Select", dapm_enum_ad3_sel),
+};
+
+/* AD5 selector control */
+static const char *enum_ad5_sel[] = {"Mic 2", "DMic 5"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad5_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD5SEL, enum_ad5_sel);
+
+static const struct snd_kcontrol_new dapm_ad5_select[] = {
+ SOC_DAPM_ENUM("AD 5 Select", dapm_enum_ad5_sel),
+};
+
+/* AD6 selector control */
+static const char *enum_ad6_sel[] = {"Mic 1A/1B", "DMic 6"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad6_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD6SEL, enum_ad6_sel);
+
+static const struct snd_kcontrol_new dapm_ad6_select[] = {
+ SOC_DAPM_ENUM("AD 6 Select", dapm_enum_ad6_sel),
+};
+
+/* ANC input selector control */
+static const char *enum_anc_in_sel[] = {"AD 6", "AD 5"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_in_sel, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_ANCINSEL, enum_anc_in_sel);
+
+static const struct snd_kcontrol_new dapm_anc_in_select[] = {
+ SOC_DAPM_ENUM("ANC Input Select", dapm_enum_anc_in_sel),
+};
+
+/* STFIR1 input selector control */
+static const char *enum_stfir1_in_sel[] = {
+ "LineIn Left", "LineIn Right", "Mic 1A/1B", "Headset Left"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir1_in_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_FIRSID1SEL, enum_stfir1_in_sel);
+
+static const struct snd_kcontrol_new dapm_stfir1_in_select[] = {
+ SOC_DAPM_ENUM("STFIR1 Input Select", dapm_enum_stfir1_in_sel),
+};
+
+/* STFIR2 input selector control */
+static const char *enum_stfir2_in_sel[] = {
+ "LineIn Right", "Mic 1A/1B", "DMic 4", "Headset Right"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir2_in_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_FIRSID2SEL, enum_stfir2_in_sel);
+
+static const struct snd_kcontrol_new dapm_stfir2_in_select[] = {
+ SOC_DAPM_ENUM("STFIR2 Input Select", dapm_enum_stfir2_in_sel),
+};
+
+static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = {
+ /* Headset path */
+
+ SND_SOC_DAPM_AIF_IN("DA_IN1", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN2", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+
+ /* XXX SwapDA12_34 */
+
+ SND_SOC_DAPM_MIXER("DA1 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA2 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("HSL Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("HSL DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHSL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHSR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("HSL DAC Driver", REG_ANACONF3,
+ REG_ANACONF3_ENDRVHSL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR DAC Driver", REG_ANACONF3,
+ REG_ANACONF3_ENDRVHSR, 0, NULL, 0),
+
+ SND_SOC_DAPM_SWITCH("Headset Left", SND_SOC_NOPM, 0, 0, dapm_hsl_mute),
+ SND_SOC_DAPM_SWITCH("Headset Right", SND_SOC_NOPM, 0, 0, dapm_hsr_mute),
+
+ SND_SOC_DAPM_MIXER("HSL Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHSL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHSR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("Charge Pump", REG_ANACONF5,
+ REG_ANACONF5_ENCPHS, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("HSL"),
+ SND_SOC_DAPM_OUTPUT("HSR"),
+
+
+ /* Earpiece path */
+
+ SND_SOC_DAPM_MIXER("EAR DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACEAR, 0, NULL, 0),
+
+ SND_SOC_DAPM_SWITCH("Earpiece", SND_SOC_NOPM, 0, 0, dapm_ear_mute),
+
+ SND_SOC_DAPM_MIXER("EAR Enable", REG_ANACONF4,
+ REG_ANACONF4_ENEAR, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("EAR"),
+
+
+ /* Handsfree path */
+
+ SND_SOC_DAPM_AIF_IN("DA_IN3", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN4", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+
+ /* XXX SwapDA12_34 */
+
+ SND_SOC_DAPM_MIXER("DA3 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA3, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA4 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA4, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Handsfree Left Select Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_ihfl_select),
+ SND_SOC_DAPM_MUX("Handsfree Right Select Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_ihfr_select),
+
+ SND_SOC_DAPM_SWITCH("Handsfree Left",
+ SND_SOC_NOPM, 0, 0, dapm_ihfl_mute),
+ SND_SOC_DAPM_SWITCH("Handsfree Right",
+ SND_SOC_NOPM, 0, 0, dapm_ihfr_mute),
+
+ SND_SOC_DAPM_MIXER("IHFL DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHFL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("IHFR DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHFR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("IHFL Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHFL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("IHFR Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHFR, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("IHFL"),
+ SND_SOC_DAPM_OUTPUT("IHFR"),
+
+
+ /* Vibrator path */
+
+ SND_SOC_DAPM_AIF_IN("DA_IN5", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN6", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MIXER("DA5 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA5, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA6 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA6, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("VIB1 DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACVIB1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("VIB2 DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACVIB2, 0, NULL, 0),
+
+ /* XXX PWM Gen */
+
+ SND_SOC_DAPM_MIXER("VIB1 Enable", REG_ANACONF4,
+ REG_ANACONF4_ENVIB1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("VIB2 Enable", REG_ANACONF4,
+ REG_ANACONF4_ENVIB2, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("VIB1"),
+ SND_SOC_DAPM_OUTPUT("VIB2"),
+
+
+ /* LineIn & Microphone 2 path */
+
+ SND_SOC_DAPM_INPUT("LINL"),
+ SND_SOC_DAPM_INPUT("LINR"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+
+ SND_SOC_DAPM_SWITCH("LineIn Left", SND_SOC_NOPM, 0, 0, dapm_linl_mute),
+ SND_SOC_DAPM_SWITCH("LineIn Right", SND_SOC_NOPM, 0, 0, dapm_linr_mute),
+ SND_SOC_DAPM_SWITCH("Mic 2", SND_SOC_NOPM, 0, 0, dapm_mic2_mute),
+
+ SND_SOC_DAPM_MIXER("LINL Enable", REG_ANACONF2,
+ REG_ANACONF2_ENLINL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LINR Enable", REG_ANACONF2,
+ REG_ANACONF2_ENLINR, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("MIC2 Enable", REG_ANACONF2,
+ REG_ANACONF2_ENMIC2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Mic 2 / LINR Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_mic2lr_select),
+
+ SND_SOC_DAPM_MIXER("LINL ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCLINL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LINR ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCLINR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("AD 1 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad1_select),
+ SND_SOC_DAPM_MUX("AD 2 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad2_select),
+
+ SND_SOC_DAPM_MIXER("AD1 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD2 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD1 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD12, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD2 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD12, 0, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AD_OUT1", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT2", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+
+
+ /* Microphone 1 path */
+
+ SND_SOC_DAPM_INPUT("MIC1A"),
+ SND_SOC_DAPM_INPUT("MIC1B"),
+
+ SND_SOC_DAPM_MUX("Mic 1A/1B Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_mic1ab_select),
+
+ SND_SOC_DAPM_SWITCH("Mic 1", SND_SOC_NOPM, 0, 0, dapm_mic1_mute),
+
+ SND_SOC_DAPM_MIXER("MIC1 Enable", REG_ANACONF2,
+ REG_ANACONF2_ENMIC1, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("MIC1 ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCMIC, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("AD 3 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad3_select),
+
+ SND_SOC_DAPM_MIXER("AD3 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD3 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD34, 0, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AD_OUT3", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+
+
+ /* HD Capture path */
+
+ SND_SOC_DAPM_MUX("AD 5 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad5_select),
+ SND_SOC_DAPM_MUX("AD 6 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad6_select),
+
+ SND_SOC_DAPM_MIXER("AD5 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD6 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD57 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD57, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD68 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD57, 0, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AD_OUT57", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT68", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+
+
+ /* Digital Microphone path */
+
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+ SND_SOC_DAPM_INPUT("DMIC3"),
+ SND_SOC_DAPM_INPUT("DMIC4"),
+ SND_SOC_DAPM_INPUT("DMIC5"),
+ SND_SOC_DAPM_INPUT("DMIC6"),
+
+ SND_SOC_DAPM_SWITCH("DMic 1", SND_SOC_NOPM, 0, 0, dapm_dmic1_mute),
+ SND_SOC_DAPM_SWITCH("DMic 2", SND_SOC_NOPM, 0, 0, dapm_dmic2_mute),
+ SND_SOC_DAPM_SWITCH("DMic 3", SND_SOC_NOPM, 0, 0, dapm_dmic3_mute),
+ SND_SOC_DAPM_SWITCH("DMic 4", SND_SOC_NOPM, 0, 0, dapm_dmic4_mute),
+ SND_SOC_DAPM_SWITCH("DMic 5", SND_SOC_NOPM, 0, 0, dapm_dmic5_mute),
+ SND_SOC_DAPM_SWITCH("DMic 6", SND_SOC_NOPM, 0, 0, dapm_dmic6_mute),
+
+ SND_SOC_DAPM_MIXER("AD4 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD4 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD34, 0, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AD_OUT4", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+
+
+ /* LineIn Bypass path */
+
+ SND_SOC_DAPM_MIXER("LINL to HSL Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LINR to HSR Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+
+ /* Analog Loopback path */
+
+ SND_SOC_DAPM_MIXER("AD1 to IHFL Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD2 to IHFR Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+
+ /* Acoustical Noise Cancellation path */
+
+ SND_SOC_DAPM_MUX("ANC Input Select Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_anc_in_select),
+
+ SND_SOC_DAPM_MIXER("ANC Control", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_SWITCH("ANC to Earpiece",
+ SND_SOC_NOPM, 0, 0, dapm_anc_ear_mute),
+
+
+ /* Sidetone Filter path */
+
+ SND_SOC_DAPM_MUX("STFIR1 Input Select Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_stfir1_in_select),
+ SND_SOC_DAPM_MUX("STFIR2 Input Select Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_stfir2_in_select),
+
+ SND_SOC_DAPM_MIXER("STFIR1 Control", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("STFIR2 Control", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("STFIR1 Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("STFIR2 Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Headset path */
+
+ {"DA1 Channel Gain", NULL, "DA_IN1"},
+ {"DA2 Channel Gain", NULL, "DA_IN2"},
+
+ {"HSL Digital Gain", NULL, "DA1 Channel Gain"},
+ {"HSR Digital Gain", NULL, "DA2 Channel Gain"},
+
+ {"HSL DAC", NULL, "HSL Digital Gain"},
+ {"HSR DAC", NULL, "HSR Digital Gain"},
+
+ {"HSL DAC Driver", NULL, "HSL DAC"},
+ {"HSR DAC Driver", NULL, "HSR DAC"},
+
+ {"Headset Left", "Playback Switch", "HSL DAC Driver"},
+ {"Headset Right", "Playback Switch", "HSR DAC Driver"},
+
+ {"HSL Enable", NULL, "Headset Left"},
+ {"HSR Enable", NULL, "Headset Right"},
+
+ {"Charge Pump", NULL, "HSL Enable"},
+ {"Charge Pump", NULL, "HSR Enable"},
+
+ {"HSL", NULL, "Charge Pump"},
+ {"HSR", NULL, "Charge Pump"},
+
+
+ /* Earpiece path */
+
+ {"EAR DAC", NULL, "HSL Digital Gain"},
+
+ {"Earpiece", "Playback Switch", "EAR DAC"},
+
+ {"EAR Enable", NULL, "Earpiece"},
+
+ {"EAR", NULL, "EAR Enable"},
+
+
+ /* Handsfree path */
+
+ {"DA3 Channel Gain", NULL, "DA_IN3"},
+ {"DA4 Channel Gain", NULL, "DA_IN4"},
+
+ {"Handsfree Left Select Playback Route", "DA 3", "DA3 Channel Gain"},
+ {"Handsfree Right Select Playback Route", "DA 4", "DA4 Channel Gain"},
+
+ {"Handsfree Left", "Playback Switch",
+ "Handsfree Left Select Playback Route"},
+ {"Handsfree Right", "Playback Switch",
+ "Handsfree Right Select Playback Route"},
+
+ {"IHFL DAC", NULL, "Handsfree Left"},
+ {"IHFR DAC", NULL, "Handsfree Right"},
+
+ {"IHFL Enable", NULL, "IHFL DAC"},
+ {"IHFR Enable", NULL, "IHFR DAC"},
+
+ {"IHFL", NULL, "IHFL Enable"},
+ {"IHFR", NULL, "IHFR Enable"},
+
+
+ /* Vibrator path */
+
+ {"DA5 Channel Gain", NULL, "DA_IN5"},
+ {"DA6 Channel Gain", NULL, "DA_IN6"},
+
+ {"VIB1 DAC", NULL, "DA5 Channel Gain"},
+ {"VIB2 DAC", NULL, "DA6 Channel Gain"},
+
+ {"VIB1 Enable", NULL, "VIB1 DAC"},
+ {"VIB2 Enable", NULL, "VIB2 DAC"},
+
+ {"VIB1", NULL, "VIB1 Enable"},
+ {"VIB2", NULL, "VIB2 Enable"},
+
+
+ /* LineIn & Microphone 2 path */
+
+ {"LineIn Left", "Capture Switch", "LINL"},
+ {"LineIn Right", "Capture Switch", "LINR"},
+ {"Mic 2", "Capture Switch", "MIC2"},
+
+ {"LINL Enable", NULL, "LineIn Left"},
+ {"LINR Enable", NULL, "LineIn Right"},
+ {"MIC2 Enable", NULL, "Mic 2"},
+
+ {"Mic 2 / LINR Select Capture Route", "LineIn Right", "LINR Enable"},
+ {"Mic 2 / LINR Select Capture Route", "Mic 2", "MIC2 Enable"},
+
+ {"LINL ADC", NULL, "LINL Enable"},
+ {"LINR ADC", NULL, "Mic 2 / LINR Select Capture Route"},
+
+ {"AD 1 Select Capture Route", "LineIn Left", "LINL ADC"},
+ {"AD 2 Select Capture Route", "LineIn Right", "LINR ADC"},
+
+ {"AD1 Channel Gain", NULL, "AD 1 Select Capture Route"},
+ {"AD2 Channel Gain", NULL, "AD 2 Select Capture Route"},
+
+ {"AD1 Enable", NULL, "AD1 Channel Gain"},
+ {"AD2 Enable", NULL, "AD2 Channel Gain"},
+
+ {"AD_OUT1", NULL, "AD1 Enable"},
+ {"AD_OUT2", NULL, "AD2 Enable"},
+
+
+ /* Microphone 1 path */
+
+ {"Mic 1A/1B Select Capture Route", "Mic 1A", "MIC1A"},
+ {"Mic 1A/1B Select Capture Route", "Mic 1B", "MIC1B"},
+
+ {"Mic 1", "Capture Switch", "Mic 1A/1B Select Capture Route"},
+
+ {"MIC1 Enable", NULL, "Mic 1"},
+
+ {"MIC1 ADC", NULL, "MIC1 Enable"},
+
+ {"AD 3 Select Capture Route", "Mic 1A/1B", "MIC1 ADC"},
+
+ {"AD3 Channel Gain", NULL, "AD 3 Select Capture Route"},
+
+ {"AD3 Enable", NULL, "AD3 Channel Gain"},
+
+ {"AD_OUT3", NULL, "AD3 Enable"},
+
+
+ /* HD Capture path */
+
+ {"AD 5 Select Capture Route", "Mic 2", "LINR ADC"},
+ {"AD 6 Select Capture Route", "Mic 1A/1B", "MIC1 ADC"},
+
+ {"AD5 Channel Gain", NULL, "AD 5 Select Capture Route"},
+ {"AD6 Channel Gain", NULL, "AD 6 Select Capture Route"},
+
+ {"AD57 Enable", NULL, "AD5 Channel Gain"},
+ {"AD68 Enable", NULL, "AD6 Channel Gain"},
+
+ {"AD_OUT57", NULL, "AD57 Enable"},
+ {"AD_OUT68", NULL, "AD68 Enable"},
+
+
+ /* Digital Microphone path */
+
+ {"DMic 1", "Capture Switch", "DMIC1"},
+ {"DMic 2", "Capture Switch", "DMIC2"},
+ {"DMic 3", "Capture Switch", "DMIC3"},
+ {"DMic 4", "Capture Switch", "DMIC4"},
+ {"DMic 5", "Capture Switch", "DMIC5"},
+ {"DMic 6", "Capture Switch", "DMIC6"},
+
+ {"AD 1 Select Capture Route", "DMic 1", "DMic 1"},
+ {"AD 2 Select Capture Route", "DMic 2", "DMic 2"},
+ {"AD 3 Select Capture Route", "DMic 3", "DMic 3"},
+ {"AD 5 Select Capture Route", "DMic 5", "DMic 5"},
+ {"AD 6 Select Capture Route", "DMic 6", "DMic 6"},
+
+ {"AD4 Channel Gain", NULL, "DMic 4"},
+
+ {"AD4 Enable", NULL, "AD4 Channel Gain"},
+
+ {"AD_OUT4", NULL, "AD4 Enable"},
+
+
+ /* LineIn Bypass path */
+
+ {"LINL to HSL Gain", NULL, "LINL Enable"},
+ {"LINR to HSR Gain", NULL, "LINR Enable"},
+
+ {"HSL DAC Driver", NULL, "LINL to HSL Gain"},
+ {"HSR DAC Driver", NULL, "LINR to HSR Gain"},
+
+
+ /* Analog Loopback path */
+
+ {"AD1 to IHFL Gain", NULL, "AD1 Channel Gain"},
+ {"AD2 to IHFR Gain", NULL, "AD2 Channel Gain"},
+
+ {"IHFL DAC", NULL, "AD1 to IHFL Gain"},
+ {"IHFR DAC", NULL, "AD2 to IHFR Gain"},
+
+
+ /* Acoustical Noise Cancellation path */
+
+ {"ANC Input Select Playback Route", "AD 5", "AD5 Channel Gain"},
+ {"ANC Input Select Playback Route", "AD 6", "AD6 Channel Gain"},
+
+ {"ANC Control", NULL, "ANC Input Select Playback Route"},
+
+ {"Handsfree Left Select Playback Route", "ANC", "ANC Control"},
+ {"Handsfree Right Select Playback Route", "ANC", "ANC Control"},
+ {"ANC to Earpiece", "Playback Switch", "ANC Control"},
+
+ {"HSL Digital Gain", NULL, "ANC to Earpiece"},
+
+
+ /* Sidetone Filter path */
+
+ {"STFIR1 Input Select Playback Route", "LineIn Left", "AD1 Enable"},
+ {"STFIR1 Input Select Playback Route", "LineIn Right", "AD2 Enable"},
+ {"STFIR1 Input Select Playback Route", "Mic 1A/1B", "AD3 Enable"},
+ {"STFIR1 Input Select Playback Route", "Headset Left", "DA_IN1"},
+ {"STFIR2 Input Select Playback Route", "LineIn Right", "AD2 Enable"},
+ {"STFIR2 Input Select Playback Route", "Mic 1A/1B", "AD3 Enable"},
+ {"STFIR2 Input Select Playback Route", "DMic 4", "AD4 Enable"},
+ {"STFIR2 Input Select Playback Route", "Headset Right", "DA_IN2"},
+
+ {"STFIR1 Control", NULL, "STFIR1 Input Select Playback Route"},
+ {"STFIR2 Control", NULL, "STFIR2 Input Select Playback Route"},
+
+ {"STFIR1 Gain", NULL, "STFIR1 Control"},
+ {"STFIR2 Gain", NULL, "STFIR2 Control"},
+
+ {"DA1 Channel Gain", NULL, "STFIR1 Gain"},
+ {"DA2 Channel Gain", NULL, "STFIR2 Gain"},
+};
+
+/* from -31 to 31 dB in 1 dB steps (mute instead of -32 dB) */
+static DECLARE_TLV_DB_SCALE(adx_dig_gain_tlv, -3200, 100, 1);
+
+/* from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) */
+static DECLARE_TLV_DB_SCALE(dax_dig_gain_tlv, -6300, 100, 1);
+
+/* from 0 to 8 dB in 1 dB steps (mute instead of -1 dB) */
+static DECLARE_TLV_DB_SCALE(hs_ear_dig_gain_tlv, -100, 100, 1);
+
+/* from -30 to 0 dB in 1 dB steps (mute instead of -31 dB) */
+static DECLARE_TLV_DB_SCALE(stfir_dig_gain_tlv, -3100, 100, 1);
+
+/* from -32 to -20 dB in 4 dB steps / from -18 to 2 dB in 2 dB steps */
+static const unsigned int hs_gain_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 3, TLV_DB_SCALE_ITEM(-3200, 400, 0),
+ 4, 15, TLV_DB_SCALE_ITEM(-1800, 200, 0),
+};
+
+/* from 0 to 31 dB in 1 dB steps */
+static DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0);
+
+/* from -10 to 20 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(lin_gain_tlv, -1000, 200, 0);
+
+/* from -36 to 0 dB in 2 dB steps (mute instead of -38 dB) */
+static DECLARE_TLV_DB_SCALE(lin2hs_gain_tlv, -3800, 200, 1);
+
+static const char *enum_ena_dis[] = {"Enabled", "Disabled"};
+static const char *enum_dis_ena[] = {"Disabled", "Enabled"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_hshpen,
+ REG_ANACONF1, REG_ANACONF1_HSHPEN, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_hslowpow,
+ REG_ANACONF1, REG_ANACONF1_HSLOWPOW, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow0,
+ REG_ANACONF1, REG_ANACONF1_DACLOWPOW0, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow1,
+ REG_ANACONF1, REG_ANACONF1_DACLOWPOW1, enum_ena_dis);
+static SOC_ENUM_SINGLE_DECL(soc_enum_earlowpow,
+ REG_ANACONF1, REG_ANACONF1_EARLOWPOW, enum_dis_ena);
+
+static const char *enum_earselcm[] = {"0.95V", "1.10V", "1.27V", "1.58V"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_earselcm,
+ REG_ANACONF1, REG_ANACONF1_EARSELCM, enum_earselcm);
+
+static const char *enum_hsfadspeed[] = {"2ms", "0.5ms", "10.6ms", "5ms"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_hsfadspeed,
+ REG_DIGMICCONF, REG_DIGMICCONF_HSFADSPEED, enum_hsfadspeed);
+
+static const char *enum_ensemicx[] = {"Differential", "Single Ended"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic1,
+ REG_ANAGAIN1, REG_ANAGAINX_ENSEMICX, enum_ensemicx);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic2,
+ REG_ANAGAIN2, REG_ANAGAINX_ENSEMICX, enum_ensemicx);
+static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic1,
+ REG_ANAGAIN1, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic2,
+ REG_ANAGAIN2, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12nh, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD1NH, REG_ADFILTCONF_AD2NH, enum_ena_dis);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34nh, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD3NH, REG_ADFILTCONF_AD4NH, enum_ena_dis);
+
+static const char *enum_av_mode[] = {"Audio", "Voice"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12voice, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD1VOICE, REG_ADFILTCONF_AD2VOICE, enum_av_mode);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34voice, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD3VOICE, REG_ADFILTCONF_AD4VOICE, enum_av_mode);
+
+static SOC_ENUM_SINGLE_DECL(soc_enum_da12voice,
+ REG_DASLOTCONF1, REG_DASLOTCONF1_DA12VOICE, enum_av_mode);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da34voice,
+ REG_DASLOTCONF3, REG_DASLOTCONF3_DA34VOICE, enum_av_mode);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da56voice,
+ REG_DASLOTCONF5, REG_DASLOTCONF5_DA56VOICE, enum_av_mode);
+
+static SOC_ENUM_SINGLE_DECL(soc_enum_swapda12_34,
+ REG_DASLOTCONF1, REG_DASLOTCONF1_SWAPDA12_34, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_vib12swap, REG_CLASSDCONF1,
+ REG_CLASSDCONF1_VIB1SWAPEN, REG_CLASSDCONF1_VIB2SWAPEN, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_hflrswap, REG_CLASSDCONF1,
+ REG_CLASSDCONF1_HFLSWAPEN, REG_CLASSDCONF1_HFRSWAPEN, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_fir01byp, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_FIRBYP0, REG_CLASSDCONF2_FIRBYP1, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_fir23byp, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_FIRBYP2, REG_CLASSDCONF2_FIRBYP3, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol01, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_HIGHVOLEN0, REG_CLASSDCONF2_HIGHVOLEN1, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol23, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_HIGHVOLEN2, REG_CLASSDCONF2_HIGHVOLEN3, enum_dis_ena);
+
+static const char *enum_sinc53[] = {"Sinc 5", "Sinc 3"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic12sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC1SINC3, REG_DMICFILTCONF_DMIC2SINC3, enum_sinc53);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic34sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC3SINC3, REG_DMICFILTCONF_DMIC4SINC3, enum_sinc53);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic56sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC5SINC3, REG_DMICFILTCONF_DMIC6SINC3, enum_sinc53);
+
+static const char *enum_da2hslr[] = {"Sidetone", "Headset"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_da2hslr, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_DATOHSLEN, REG_DIGMULTCONF1_DATOHSREN, enum_da2hslr);
+
+static const char *enum_sinc31[] = {"Sinc 3", "Sinc 1"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_hsesinc,
+ REG_HSLEARDIGGAIN, REG_HSLEARDIGGAIN_HSSINC1, enum_sinc31);
+
+static const char *enum_fadespeed[] = {"1ms", "4ms", "8ms", "16ms"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_fadespeed,
+ REG_HSRDIGGAIN, REG_HSRDIGGAIN_FADESPEED, enum_fadespeed);
+
+static struct snd_kcontrol_new ab8500_snd_controls[] = {
+ SOC_ENUM("Headset High Pass Playback Switch", soc_enum_hshpen),
+ SOC_ENUM("Headset Low Power Playback Switch", soc_enum_hslowpow),
+ SOC_ENUM("Headset DAC Low Power Playback Switch", soc_enum_daclowpow0),
+ SOC_ENUM("Headset DAC RTZ Func Playback Switch", soc_enum_daclowpow1),
+ SOC_ENUM("Ear Low Power Playback Switch", soc_enum_earlowpow),
+ SOC_ENUM("Ear Nominal Common Mode Playback Switch", soc_enum_earselcm),
+
+ SOC_ENUM("Headset Fade Speed Playback Switch", soc_enum_hsfadspeed),
+
+ SOC_ENUM("Mic 1 Type Capture Switch", soc_enum_ensemic1),
+ SOC_ENUM("Mic 2 Type Capture Switch", soc_enum_ensemic2),
+ SOC_ENUM("Mic 1 Low Power Capture Switch", soc_enum_lowpowmic1),
+ SOC_ENUM("Mic 2 Low Power Capture Switch", soc_enum_lowpowmic2),
+
+ SOC_ENUM("LineIn High Pass Capture Switch", soc_enum_ad12nh),
+ SOC_ENUM("Mic High Pass Capture Switch", soc_enum_ad34nh),
+ SOC_ENUM("LineIn Mode Capture Switch", soc_enum_ad12voice),
+ SOC_ENUM("Mic Mode Capture Switch", soc_enum_ad34voice),
+
+ SOC_ENUM("Headset/Ear Mode Playback Switch", soc_enum_da12voice),
+ SOC_ENUM("Handsfree Mode Playback Switch", soc_enum_da34voice),
+ SOC_ENUM("Vibrator Mode Playback Switch", soc_enum_da56voice),
+
+ SOC_ENUM("DA 1-2 and 3-4 Swap Playback Switch", soc_enum_swapda12_34),
+
+ SOC_ENUM("Handsfree Low EMI Mode Playback Switch", soc_enum_hflrswap),
+ SOC_ENUM("Vibrator Low EMI Mode Playback Switch", soc_enum_vib12swap),
+
+ SOC_ENUM("Handsfree FIR Bypass Playback Switch", soc_enum_fir01byp),
+ SOC_ENUM("Vibrator FIR Bypass Playback Switch", soc_enum_fir23byp),
+
+ /* XXX Cannot be changed on the fly with digital channel enabled. */
+ SOC_ENUM("Handsfree High Volume Playback Switch", soc_enum_highvol01),
+ SOC_ENUM("Vibrator High Volume Playback Switch", soc_enum_highvol23),
+
+ SOC_SINGLE("ClassD High Pass Gain Playback Volume",
+ REG_CLASSDCONF3, REG_CLASSDCONF3_DITHHPGAIN,
+ REG_CLASSDCONF3_DITHHPGAIN_MAX, NORMAL),
+ SOC_SINGLE("ClassD White Gain Playback Volume",
+ REG_CLASSDCONF3, REG_CLASSDCONF3_DITHWGAIN,
+ REG_CLASSDCONF3_DITHWGAIN_MAX, NORMAL),
+
+ SOC_ENUM("DMic 1-2 Filter Capture Switch", soc_enum_dmic12sinc),
+ SOC_ENUM("DMic 3-4 Filter Capture Switch", soc_enum_dmic34sinc),
+ SOC_ENUM("DMic 5-6 Filter Capture Switch", soc_enum_dmic56sinc),
+
+ /* Whether DA_IN 1-2 are guided to sidetone filters' inputs. */
+ SOC_ENUM("DA_IN Select Playback Route", soc_enum_da2hslr),
+
+ /* XXX Cannot be changed on the fly with digital channel enabled. */
+ SOC_ENUM("Headset/Ear Filter Playback Switch", soc_enum_hsesinc),
+
+ SOC_ENUM("Digital Gain Fade Speed Switch", soc_enum_fadespeed),
+
+ /* Digital gains for AD side */
+
+ SOC_DOUBLE_R_TLV("LineIn Master Gain Capture Volume",
+ REG_ADDIGGAIN1, REG_ADDIGGAIN2,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Mic Master Gain Capture Volume",
+ REG_ADDIGGAIN3, REG_ADDIGGAIN4,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("HD Mic Master Gain Capture Volume",
+ REG_ADDIGGAIN5, REG_ADDIGGAIN6,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+
+ /* Digital gains for DA side */
+
+ SOC_DOUBLE_R_TLV("Headset Master Gain Playback Volume",
+ REG_DADIGGAIN1, REG_DADIGGAIN2,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Handsfree Master Gain Playback Volume",
+ REG_DADIGGAIN3, REG_DADIGGAIN4,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Vibrator Master Gain Playback Volume",
+ REG_DADIGGAIN5, REG_DADIGGAIN6,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Analog Loopback Gain Playback Volume",
+ REG_ADDIGLOOPGAIN1, REG_ADDIGLOOPGAIN2,
+ 0, REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Headset Digital Gain Playback Volume",
+ REG_HSLEARDIGGAIN, REG_HSRDIGGAIN,
+ 0, REG_HSLEARDIGGAIN_HSLDGAIN_MAX, INVERT, hs_ear_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Sidetone Digital Gain Playback Volume",
+ REG_SIDFIRGAIN1, REG_SIDFIRGAIN2,
+ 0, REG_SIDFIRGAINX_FIRSIDXGAIN_MAX, INVERT, stfir_dig_gain_tlv),
+
+ /* Analog gains */
+ SOC_DOUBLE_TLV("Headset Gain Playback Volume",
+ REG_ANAGAIN3,
+ REG_ANAGAIN3_HSLGAIN, REG_ANAGAIN3_HSRGAIN,
+ REG_ANAGAIN3_HSXGAIN_MAX, INVERT, hs_gain_tlv),
+ SOC_SINGLE_TLV("Mic 1 Capture Volume",
+ REG_ANAGAIN1,
+ REG_ANAGAINX_MICXGAIN,
+ REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv),
+ SOC_SINGLE_TLV("Mic 2 Capture Volume",
+ REG_ANAGAIN2,
+ REG_ANAGAINX_MICXGAIN,
+ REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv),
+ SOC_DOUBLE_TLV("LineIn Capture Volume",
+ REG_ANAGAIN4,
+ REG_ANAGAIN4_LINLGAIN, REG_ANAGAIN4_LINRGAIN,
+ REG_ANAGAIN4_LINXGAIN_MAX, NORMAL, lin_gain_tlv),
+ SOC_DOUBLE_R_TLV("LineIn to Headset Bypass Playback Volume",
+ REG_DIGLINHSLGAIN, REG_DIGLINHSRGAIN,
+ REG_DIGLINHSXGAIN_LINTOHSXGAIN,
+ REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX, INVERT, lin2hs_gain_tlv),
+};
+
+static int ab8500_add_widgets(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(codec, ab8500_dapm_widgets,
+ ARRAY_SIZE(ab8500_dapm_widgets));
+ if (ret < 0) {
+ pr_err("%s: Failed to create DAPM controls (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+ if (ret < 0) {
+ pr_err("%s: Failed to add DAPM routes (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ab8500_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+ return 0;
+}
+
+static int ab8500_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+ return 0;
+}
+
+static int ab8500_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ /* Clear interrupt status registers by reading them. */
+ ab8500_audio_read_reg(dai->codec, REG_AUDINTSOURCE1);
+ ab8500_audio_read_reg(dai->codec, REG_AUDINTSOURCE2);
+
+ return 0;
+}
+
+static void ab8500_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+ ab8500_audio_dump_all_reg(dai->codec);
+}
+
+static int ab8500_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ pr_debug("%s Enter.\n", __func__);
+ return 0;
+}
+
+/* Sets Master/Slave relations according format mask */
+static int set_dai_relationship(struct snd_soc_codec *codec, unsigned int fmt)
+{
+ unsigned int clear_mask;
+ unsigned int set_mask;
+
+ clear_mask = BMASK(REG_DIGIFCONF3_IF1DATOIF0AD) |
+ BMASK(REG_DIGIFCONF3_IF1CLKTOIF0CLK) |
+ BMASK(REG_DIGIFCONF3_IF0BFIFOEN) |
+ BMASK(REG_DIGIFCONF3_IF0MASTER);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */
+ pr_info("- Codec is a master\n");
+ set_mask |= BMASK(REG_DIGIFCONF3_IF0MASTER);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */
+ pr_info("- Codec is a slave\n");
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */
+ case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
+ pr_err("The device is either a master or a slave.\n");
+ default:
+ pr_err("Unsupporter master mask 0x%x\n",
+ (fmt & SND_SOC_DAIFMT_MASTER_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ ab8500_update_audio_reg(codec, REG_DIGIFCONF3, clear_mask, set_mask);
+
+ return 0;
+}
+
+/* Gates clocking according format mask */
+static int set_dai_clock_gate(struct snd_soc_codec *codec, unsigned int fmt)
+{
+ unsigned int clear_mask;
+ unsigned int set_mask;
+
+ clear_mask = BMASK(REG_DIGIFCONF1_ENMASTGEN) |
+ BMASK(REG_DIGIFCONF1_ENFSBITCLK0);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT: /* continuous clock */
+ pr_info("- Clock is not gated\n");
+ set_mask |= BMASK(REG_DIGIFCONF1_ENMASTGEN);
+ set_mask |= BMASK(REG_DIGIFCONF1_ENFSBITCLK0);
+ break;
+ case SND_SOC_DAIFMT_GATED: /* clock is gated */
+ pr_info("- Clock IS gated\n");
+ break;
+ default:
+ pr_err("Unsupporter clock mask 0x%x\n",
+ (fmt & SND_SOC_DAIFMT_CLOCK_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ ab8500_update_audio_reg(codec, REG_DIGIFCONF1, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ unsigned int clear_mask;
+ unsigned int set_mask;
+ struct snd_soc_codec *codec = dai->codec;
+ int err;
+
+ pr_debug("%s: fmt = 0x%x\n", __func__, fmt);
+
+ /* Set Master/Slave */
+ err = set_dai_relationship(codec, fmt);
+ if (err) {
+ pr_err("%s: Failed to set master/slave (%d).\n", __func__, err);
+ return err;
+ }
+
+ /* Set clock enable/disable */
+ err = set_dai_clock_gate(codec, fmt);
+ if (err) {
+ pr_err("%s: Failed to set clock gate (%d).\n", __func__, err);
+ return err;
+ }
+
+ /* Setting data transfer format */
+ clear_mask = REG_MASK_ALL;
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: /* I2S mode */
+ pr_info("- FORMAT I2S\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT1);
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0DEL);
+
+ /* 32 bit */
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1) |
+ BMASK(REG_DIGIFCONF2_IF0WL0);
+ break;
+ case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */
+ pr_info("- FORMAT DSP A\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0);
+ break;
+ case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */
+ pr_info("- FORMAT DSP B\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0);
+ break;
+ default:
+ pr_err("Unsupporter format 0x%x\n",
+ (fmt & SND_SOC_DAIFMT_FORMAT_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
+ pr_info("- Normal bit clock, normal frame\n");
+ break;
+ case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */
+ pr_info("- Normal bit clock, inverted frame\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P);
+ break;
+ case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */
+ pr_info("- inverted bit clock, normal frame\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P);
+ break;
+ case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */
+ pr_info("- inverted bit clock, inverted frame\n");
+ set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P);
+ set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P);
+ break;
+ default:
+ pr_err("Unsupported INV mask 0x%x\n",
+ (fmt & SND_SOC_DAIFMT_INV_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ ab8500_update_audio_reg(codec, REG_DIGIFCONF2, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_set_dai_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ int data;
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned int clear_mask;
+
+ /* Only 16 bit slot width is supported at the moment in TDM mode */
+ if (slot_width != 16) {
+ pr_err("%s: Unsupported slot_width %d.\n",
+ __func__, slot_width);
+ return -EINVAL;
+ }
+
+ /* Set the TDM clocking according to slot count */
+ switch (slots) {
+ case 2:
+ data = REG_MASK_NONE;
+ break;
+ case 4:
+ data = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0);
+ break;
+ case 8:
+ data = BMASK(REG_DIGIFCONF1_IF0BITCLKOS1);
+ break;
+ case 16:
+ data = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) |
+ BMASK(REG_DIGIFCONF1_IF0BITCLKOS1);
+ break;
+ default:
+ pr_err("%s: Unsupported slots %d.\n", __func__, slots);
+ return -EINVAL;
+ }
+
+ ab8500_update_audio_reg(codec, REG_DIGIFCONF1,
+ BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) |
+ BMASK(REG_DIGIFCONF1_IF0BITCLKOS1),
+ data);
+
+ /* Mask to clear RX TDM slots reception for DA path*/
+ clear_mask = BMASK(REG_DASLOTCONFX_SLTODAX4) |
+ BMASK(REG_DASLOTCONFX_SLTODAX3) |
+ BMASK(REG_DASLOTCONFX_SLTODAX2) |
+ BMASK(REG_DASLOTCONFX_SLTODAX1) |
+ BMASK(REG_DASLOTCONFX_SLTODAX0);
+
+ /* XXX Make slot configuration as a control */
+
+ /* DA_IN1 receives slot 9 */
+ ab8500_update_audio_reg(codec, REG_DASLOTCONF1,
+ clear_mask,
+ BMASK(REG_DASLOTCONFX_SLTODAX3) |
+ BMASK(REG_DASLOTCONFX_SLTODAX0));
+
+ /* DA_IN2 receives slot 11 */
+ ab8500_update_audio_reg(codec, REG_DASLOTCONF2,
+ clear_mask,
+ BMASK(REG_DASLOTCONFX_SLTODAX3) |
+ BMASK(REG_DASLOTCONFX_SLTODAX1) |
+ BMASK(REG_DASLOTCONFX_SLTODAX0));
+
+ /* DA_IN3 receives slot 9 */
+ ab8500_update_audio_reg(codec, REG_DASLOTCONF3,
+ clear_mask,
+ BMASK(REG_DASLOTCONFX_SLTODAX3) |
+ BMASK(REG_DASLOTCONFX_SLTODAX0));
+
+ /* DA_IN4 receives slot 11 */
+ ab8500_update_audio_reg(codec, REG_DASLOTCONF4,
+ clear_mask,
+ BMASK(REG_DASLOTCONFX_SLTODAX3) |
+ BMASK(REG_DASLOTCONFX_SLTODAX1) |
+ BMASK(REG_DASLOTCONFX_SLTODAX0));
+
+ /* AD_OUT3 transmits slots 0 & 1 */
+ ab8500_update_audio_reg(codec, REG_ADSLOTSEL1,
+ REG_MASK_ALL,
+ BMASK(REG_ADSLOTSEL_ODDX_1) |
+ BMASK(REG_ADSLOTSEL_EVENX_1));
+
+ return 0;
+}
+
+struct snd_soc_dai_driver ab8500_codec_dai[] = {
+ {
+ .name = "ab8500-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "ab8500_0p",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AB8500_SUPPORTED_RATE,
+ .formats = AB8500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab8500_pcm_startup,
+ .prepare = ab8500_pcm_prepare,
+ .hw_params = ab8500_pcm_hw_params,
+ .shutdown = ab8500_pcm_shutdown,
+ .set_sysclk = ab8500_set_dai_sysclk,
+ .set_tdm_slot = ab8500_set_dai_tdm_slot,
+ .set_fmt = ab8500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1
+ },
+ {
+ .name = "ab8500-codec-dai.1",
+ .id = 1,
+ .capture = {
+ .stream_name = "ab8500_0c",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = AB8500_SUPPORTED_RATE,
+ .formats = AB8500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab8500_pcm_startup,
+ .prepare = ab8500_pcm_prepare,
+ .hw_params = ab8500_pcm_hw_params,
+ .shutdown = ab8500_pcm_shutdown,
+ .set_sysclk = ab8500_set_dai_sysclk,
+ .set_tdm_slot = ab8500_set_dai_tdm_slot,
+ .set_fmt = ab8500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1
+ }
+};
+
+/* Configures audio macrocell into the AB8500 Chip */
+static void configure_audio_macrocell(struct snd_soc_codec *codec)
+{
+ int data;
+
+ data = ab8500_read_reg(codec, AB8500_SYS_CTRL2_BLOCK, AB8500_CTRL3_REG);
+ data &= ~CLK_32K_OUT2_DISABLE;
+ ab8500_write_reg(codec, AB8500_SYS_CTRL2_BLOCK, AB8500_CTRL3_REG, data);
+ data |= INACTIVE_RESET_AUDIO;
+ ab8500_write_reg(codec, AB8500_SYS_CTRL2_BLOCK, AB8500_CTRL3_REG, data);
+
+ data = ab8500_read_reg(codec, AB8500_SYS_CTRL2_BLOCK,
+ AB8500_SYSULPCLK_CTRL1_REG);
+ data |= ENABLE_AUDIO_CLK_TO_AUDIO_BLK;
+ ab8500_write_reg(codec, AB8500_SYS_CTRL2_BLOCK,
+ AB8500_SYSULPCLK_CTRL1_REG, data);
+
+ data = ab8500_read_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG);
+ data |= GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | GPIO31_DIR_OUTPUT;
+ ab8500_write_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG, data);
+}
+
+static int ab8500_codec_probe(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ u8 *cache = codec->reg_cache;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ configure_audio_macrocell(codec);
+
+ for (i = REG_AUDREV; i >= REG_POWERUP; i--)
+ ab8500_audio_write_reg(codec, i, cache[i]);
+
+ ret = snd_soc_add_controls(codec, ab8500_snd_controls,
+ ARRAY_SIZE(ab8500_snd_controls));
+ if (ret < 0) {
+ pr_err("%s: failed to add soc controls (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = ab8500_add_widgets(codec);
+ if (ret < 0) {
+ pr_err("%s: Failed add widgets (%d).\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ab8500_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_free(codec);
+
+ return 0;
+}
+
+static int ab8500_codec_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_resume(struct snd_soc_codec *codec)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver ab8500_codec_drv = {
+ .probe = ab8500_codec_probe,
+ .remove = ab8500_codec_remove,
+ .suspend = ab8500_codec_suspend,
+ .resume = ab8500_codec_resume,
+ .read = ab8500_audio_read_reg,
+ .write = ab8500_audio_write_reg,
+ .reg_cache_size = ARRAY_SIZE(ab8500_reg_cache),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ab8500_reg_cache,
+};
+
+static int __devinit ab8500_codec_drv_probe(struct platform_device *pdev)
+{
+ int err;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ err = snd_soc_register_codec(&pdev->dev,
+ &ab8500_codec_drv,
+ ab8500_codec_dai,
+ ARRAY_SIZE(ab8500_codec_dai));
+
+ if (err < 0) {
+ pr_err("%s: Error: Failed to register codec (%d).\n",
+ __func__, err);
+ }
+
+ return err;
+}
+
+static int __devexit ab8500_codec_drv_remove(struct platform_device *pdev)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ snd_soc_unregister_codec(&pdev->dev);
+
+ return 0;
+}
+
+static int ab8500_codec_drv_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_drv_resume(struct platform_device *pdev)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static struct platform_driver ab8500_codec_platform_drv = {
+ .driver = {
+ .name = "ab8500-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab8500_codec_drv_probe,
+ .remove = __devexit_p(ab8500_codec_drv_remove),
+ .suspend = ab8500_codec_drv_suspend,
+ .resume = ab8500_codec_drv_resume,
+};
+
+static int __devinit ab8500_codec_platform_drv_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ret = platform_driver_register(&ab8500_codec_platform_drv);
+ if (ret != 0) {
+ pr_err("%s: Failed to register AB8500 platform driver (%d)!\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static void __exit ab8500_codec_platform_drv_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ platform_driver_unregister(&ab8500_codec_platform_drv);
+}
+
+module_init(ab8500_codec_platform_drv_init);
+module_exit(ab8500_codec_platform_drv_exit);
+
+MODULE_DESCRIPTION("AB8500 Codec driver");
+MODULE_ALIAS("platform:ab8500-codec");
+MODULE_AUTHOR("ST-Ericsson");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/ab8500.h b/sound/soc/codecs/ab8500.h
new file mode 100644
index 00000000000..5273b8b9156
--- /dev/null
+++ b/sound/soc/codecs/ab8500.h
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef AB8500_CODEC_REGISTERS_H
+#define AB8500_CODEC_REGISTERS_H
+
+extern struct snd_soc_dai_driver ab8500_codec_dai[];
+extern struct snd_soc_codec_driver soc_codec_dev_ab8500;
+
+#define AB8500_SUPPORTED_RATE (SNDRV_PCM_RATE_48000)
+
+#define AB8500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE)
+
+/* AB8500 audio bank (0x0d) register definitions */
+
+#define REG_POWERUP 0x00
+#define REG_AUDSWRESET 0x01
+#define REG_ADPATHENA 0x02
+#define REG_DAPATHENA 0x03
+#define REG_ANACONF1 0x04
+#define REG_ANACONF2 0x05
+#define REG_DIGMICCONF 0x06
+#define REG_ANACONF3 0x07
+#define REG_ANACONF4 0x08
+#define REG_DAPATHCONF 0x09
+#define REG_MUTECONF 0x0A
+#define REG_SHORTCIRCONF 0x0B
+#define REG_ANACONF5 0x0C
+#define REG_ENVCPCONF 0x0D
+#define REG_SIGENVCONF 0x0E
+#define REG_PWMGENCONF1 0x0F
+#define REG_PWMGENCONF2 0x10
+#define REG_PWMGENCONF3 0x11
+#define REG_PWMGENCONF4 0x12
+#define REG_PWMGENCONF5 0x13
+#define REG_ANAGAIN1 0x14
+#define REG_ANAGAIN2 0x15
+#define REG_ANAGAIN3 0x16
+#define REG_ANAGAIN4 0x17
+#define REG_DIGLINHSLGAIN 0x18
+#define REG_DIGLINHSRGAIN 0x19
+#define REG_ADFILTCONF 0x1A
+#define REG_DIGIFCONF1 0x1B
+#define REG_DIGIFCONF2 0x1C
+#define REG_DIGIFCONF3 0x1D
+#define REG_DIGIFCONF4 0x1E
+#define REG_ADSLOTSEL1 0x1F
+#define REG_ADSLOTSEL2 0x20
+#define REG_ADSLOTSEL3 0x21
+#define REG_ADSLOTSEL4 0x22
+#define REG_ADSLOTSEL5 0x23
+#define REG_ADSLOTSEL6 0x24
+#define REG_ADSLOTSEL7 0x25
+#define REG_ADSLOTSEL8 0x26
+#define REG_ADSLOTSEL9 0x27
+#define REG_ADSLOTSEL10 0x28
+#define REG_ADSLOTSEL11 0x29
+#define REG_ADSLOTSEL12 0x2A
+#define REG_ADSLOTSEL13 0x2B
+#define REG_ADSLOTSEL14 0x2C
+#define REG_ADSLOTSEL15 0x2D
+#define REG_ADSLOTSEL16 0x2E
+#define REG_ADSLOTHIZCTRL1 0x2F
+#define REG_ADSLOTHIZCTRL2 0x30
+#define REG_ADSLOTHIZCTRL3 0x31
+#define REG_ADSLOTHIZCTRL4 0x32
+#define REG_DASLOTCONF1 0x33
+#define REG_DASLOTCONF2 0x34
+#define REG_DASLOTCONF3 0x35
+#define REG_DASLOTCONF4 0x36
+#define REG_DASLOTCONF5 0x37
+#define REG_DASLOTCONF6 0x38
+#define REG_DASLOTCONF7 0x39
+#define REG_DASLOTCONF8 0x3A
+#define REG_CLASSDCONF1 0x3B
+#define REG_CLASSDCONF2 0x3C
+#define REG_CLASSDCONF3 0x3D
+#define REG_DMICFILTCONF 0x3E
+#define REG_DIGMULTCONF1 0x3F
+#define REG_DIGMULTCONF2 0x40
+#define REG_ADDIGGAIN1 0x41
+#define REG_ADDIGGAIN2 0x42
+#define REG_ADDIGGAIN3 0x43
+#define REG_ADDIGGAIN4 0x44
+#define REG_ADDIGGAIN5 0x45
+#define REG_ADDIGGAIN6 0x46
+#define REG_DADIGGAIN1 0x47
+#define REG_DADIGGAIN2 0x48
+#define REG_DADIGGAIN3 0x49
+#define REG_DADIGGAIN4 0x4A
+#define REG_DADIGGAIN5 0x4B
+#define REG_DADIGGAIN6 0x4C
+#define REG_ADDIGLOOPGAIN1 0x4D
+#define REG_ADDIGLOOPGAIN2 0x4E
+#define REG_HSLEARDIGGAIN 0x4F
+#define REG_HSRDIGGAIN 0x50
+#define REG_SIDFIRGAIN1 0x51
+#define REG_SIDFIRGAIN2 0x52
+#define REG_ANCCONF1 0x53
+#define REG_ANCCONF2 0x54
+#define REG_ANCCONF3 0x55
+#define REG_ANCCONF4 0x56
+#define REG_ANCCONF5 0x57
+#define REG_ANCCONF6 0x58
+#define REG_ANCCONF7 0x59
+#define REG_ANCCONF8 0x5A
+#define REG_ANCCONF9 0x5B
+#define REG_ANCCONF10 0x5C
+#define REG_ANCCONF11 0x5D
+#define REG_ANCCONF12 0x5E
+#define REG_ANCCONF13 0x5F
+#define REG_ANCCONF14 0x60
+#define REG_SIDFIRADR 0x61
+#define REG_SIDFIRCOEF1 0x62
+#define REG_SIDFIRCOEF2 0x63
+#define REG_SIDFIRCONF 0x64
+#define REG_AUDINTMASK1 0x65
+#define REG_AUDINTSOURCE1 0x66
+#define REG_AUDINTMASK2 0x67
+#define REG_AUDINTSOURCE2 0x68
+#define REG_FIFOCONF1 0x69
+#define REG_FIFOCONF2 0x6A
+#define REG_FIFOCONF3 0x6B
+#define REG_FIFOCONF4 0x6C
+#define REG_FIFOCONF5 0x6D
+#define REG_FIFOCONF6 0x6E
+#define REG_AUDREV 0x6F
+
+#define AB8500_FIRST_REG REG_POWERUP
+#define AB8500_LAST_REG REG_AUDREV
+#define AB8500_CACHEREGNUM (AB8500_LAST_REG + 1)
+
+
+#define REG_MASK_ALL 0xFF
+#define REG_MASK_NONE 0x00
+
+/* REG_POWERUP */
+#define REG_POWERUP_POWERUP 7
+#define REG_POWERUP_ENANA 3
+
+/* REG_AUDSWRESET */
+#define REG_AUDSWRESET_SWRESET 7
+
+/* REG_ADPATHENA */
+#define REG_ADPATHENA_ENAD12 7
+#define REG_ADPATHENA_ENAD34 5
+#define REG_ADPATHENA_ENAD57 3
+#define REG_ADPATHENA_ENAD68 2
+
+/* REG_DAPATHENA */
+#define REG_DAPATHENA_ENDA1 7
+#define REG_DAPATHENA_ENDA2 6
+#define REG_DAPATHENA_ENDA3 5
+#define REG_DAPATHENA_ENDA4 4
+#define REG_DAPATHENA_ENDA5 3
+#define REG_DAPATHENA_ENDA6 2
+
+/* REG_ANACONF1 */
+#define REG_ANACONF1_HSLOWPOW 7
+#define REG_ANACONF1_DACLOWPOW1 6
+#define REG_ANACONF1_DACLOWPOW0 5
+#define REG_ANACONF1_EARLOWPOW 4
+#define REG_ANACONF1_EARSELCM 2
+#define REG_ANACONF1_HSHPEN 1
+
+/* REG_ANACONF2 */
+#define REG_ANACONF2_ENMIC1 7
+#define REG_ANACONF2_ENMIC2 6
+#define REG_ANACONF2_ENLINL 5
+#define REG_ANACONF2_ENLINR 4
+#define REG_ANACONF2_MUTMIC1 3
+#define REG_ANACONF2_MUTMIC2 2
+#define REG_ANACONF2_MUTLINL 1
+#define REG_ANACONF2_MUTLINR 0
+
+/* REG_DIGMICCONF */
+#define REG_DIGMICCONF_ENDMIC1 7
+#define REG_DIGMICCONF_ENDMIC2 6
+#define REG_DIGMICCONF_ENDMIC3 5
+#define REG_DIGMICCONF_ENDMIC4 4
+#define REG_DIGMICCONF_ENDMIC5 3
+#define REG_DIGMICCONF_ENDMIC6 2
+#define REG_DIGMICCONF_HSFADSPEED 0
+
+/* REG_ANACONF3 */
+#define REG_ANACONF3_MIC1SEL 7
+#define REG_ANACONF3_LINRSEL 6
+#define REG_ANACONF3_ENDRVHSL 5
+#define REG_ANACONF3_ENDRVHSR 4
+#define REG_ANACONF3_ENADCMIC 2
+#define REG_ANACONF3_ENADCLINL 1
+#define REG_ANACONF3_ENADCLINR 0
+
+/* REG_ANACONF4 */
+#define REG_ANACONF4_DISPDVSS 7
+#define REG_ANACONF4_ENEAR 6
+#define REG_ANACONF4_ENHSL 5
+#define REG_ANACONF4_ENHSR 4
+#define REG_ANACONF4_ENHFL 3
+#define REG_ANACONF4_ENHFR 2
+#define REG_ANACONF4_ENVIB1 1
+#define REG_ANACONF4_ENVIB2 0
+
+/* REG_DAPATHCONF */
+#define REG_DAPATHCONF_ENDACEAR 6
+#define REG_DAPATHCONF_ENDACHSL 5
+#define REG_DAPATHCONF_ENDACHSR 4
+#define REG_DAPATHCONF_ENDACHFL 3
+#define REG_DAPATHCONF_ENDACHFR 2
+#define REG_DAPATHCONF_ENDACVIB1 1
+#define REG_DAPATHCONF_ENDACVIB2 0
+
+/* REG_MUTECONF */
+#define REG_MUTECONF_MUTEAR 6
+#define REG_MUTECONF_MUTHSL 5
+#define REG_MUTECONF_MUTHSR 4
+
+/* REG_SHORTCIRCONF */
+/* REG_ANACONF5 */
+#define REG_ANACONF5_ENCPHS 7
+#define REG_ANACONF5_HSAUTOEN 0
+
+/* REG_ENVCPCONF */
+#define REG_ENVCPCONF_ENVDETHTHRE_MAX 0x0F
+#define REG_ENVCPCONF_ENVDETLTHRE_MAX 0x0F
+
+/* REG_SIGENVCONF */
+#define REG_SIGENVCONF_CPLVEN 5
+#define REG_SIGENVCONF_ENVDETCPEN 4
+#define REG_SIGENVCONF_ENVDETTIME_MAX 0x0F
+
+/* REG_PWMGENCONF1 */
+#define REG_PWMGENCONF1_PWMTOVIB1 7
+#define REG_PWMGENCONF1_PWMTOVIB2 6
+#define REG_PWMGENCONF1_PWM1CTRL 5
+#define REG_PWMGENCONF1_PWM2CTRL 4
+#define REG_PWMGENCONF1_PWM1NCTRL 3
+#define REG_PWMGENCONF1_PWM1PCTRL 2
+#define REG_PWMGENCONF1_PWM2NCTRL 1
+#define REG_PWMGENCONF1_PWM2PCTRL 0
+
+/* REG_PWMGENCONF2 */
+/* REG_PWMGENCONF3 */
+/* REG_PWMGENCONF4 */
+/* REG_PWMGENCONF5 */
+/* REG_ANAGAIN1 */
+/* REG_ANAGAIN2 */
+#define REG_ANAGAINX_ENSEMICX 7
+#define REG_ANAGAINX_LOWPOWMICX 6
+#define REG_ANAGAINX_MICXGAIN 0
+#define REG_ANAGAINX_MICXGAIN_MAX 0x1F
+
+/* REG_ANAGAIN3 */
+#define REG_ANAGAIN3_HSLGAIN 4
+#define REG_ANAGAIN3_HSRGAIN 0
+#define REG_ANAGAIN3_HSXGAIN_MAX 0x0F
+
+/* REG_ANAGAIN4 */
+#define REG_ANAGAIN4_LINLGAIN 4
+#define REG_ANAGAIN4_LINRGAIN 0
+#define REG_ANAGAIN4_LINXGAIN_MAX 0x0F
+
+/* REG_DIGLINHSLGAIN */
+/* REG_DIGLINHSRGAIN */
+#define REG_DIGLINHSXGAIN_LINTOHSXGAIN 0
+#define REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX 0x13
+
+/* REG_ADFILTCONF */
+#define REG_ADFILTCONF_AD1NH 7
+#define REG_ADFILTCONF_AD2NH 6
+#define REG_ADFILTCONF_AD3NH 5
+#define REG_ADFILTCONF_AD4NH 4
+#define REG_ADFILTCONF_AD1VOICE 3
+#define REG_ADFILTCONF_AD2VOICE 2
+#define REG_ADFILTCONF_AD3VOICE 1
+#define REG_ADFILTCONF_AD4VOICE 0
+
+/* REG_DIGIFCONF1 */
+#define REG_DIGIFCONF1_ENMASTGEN 7
+#define REG_DIGIFCONF1_IF1BITCLKOS1 6
+#define REG_DIGIFCONF1_IF1BITCLKOS0 5
+#define REG_DIGIFCONF1_ENFSBITCLK1 4
+#define REG_DIGIFCONF1_IF0BITCLKOS1 2
+#define REG_DIGIFCONF1_IF0BITCLKOS0 1
+#define REG_DIGIFCONF1_ENFSBITCLK0 0
+
+/* REG_DIGIFCONF2 */
+#define REG_DIGIFCONF2_FSYNC0P 6
+#define REG_DIGIFCONF2_BITCLK0P 5
+#define REG_DIGIFCONF2_IF0DEL 4
+#define REG_DIGIFCONF2_IF0FORMAT1 3
+#define REG_DIGIFCONF2_IF0FORMAT0 2
+#define REG_DIGIFCONF2_IF0WL1 1
+#define REG_DIGIFCONF2_IF0WL0 0
+
+/* REG_DIGIFCONF3 */
+#define REG_DIGIFCONF3_IF0DATOIF1AD 7
+#define REG_DIGIFCONF3_IF0CLKTOIF1CLK 6
+#define REG_DIGIFCONF3_IF1MASTER 5
+#define REG_DIGIFCONF3_IF1DATOIF0AD 3
+#define REG_DIGIFCONF3_IF1CLKTOIF0CLK 2
+#define REG_DIGIFCONF3_IF0MASTER 1
+#define REG_DIGIFCONF3_IF0BFIFOEN 0
+
+/* REG_DIGIFCONF4 */
+
+/* REG_ADSLOTSELX */
+#define REG_ADSLOTSEL_ODDX_3 7
+#define REG_ADSLOTSEL_ODDX_2 6
+#define REG_ADSLOTSEL_ODDX_1 5
+#define REG_ADSLOTSEL_ODDX_0 4
+#define REG_ADSLOTSEL_EVENX_3 3
+#define REG_ADSLOTSEL_EVENX_2 2
+#define REG_ADSLOTSEL_EVENX_1 1
+#define REG_ADSLOTSEL_EVENX_0 0
+
+/* REG_ADSLOTHIZCTRL1 */
+/* REG_ADSLOTHIZCTRL2 */
+/* REG_ADSLOTHIZCTRL3 */
+/* REG_ADSLOTHIZCTRL4 */
+/* REG_DASLOTCONF1 */
+#define REG_DASLOTCONF1_DA12VOICE 7
+#define REG_DASLOTCONF1_SWAPDA12_34 6
+#define REG_DASLOTCONF1_DAI7TOADO1 5
+
+/* REG_DASLOTCONF2 */
+#define REG_DASLOTCONF2_DAI8TOADO2 5
+
+/* REG_DASLOTCONF3 */
+#define REG_DASLOTCONF3_DA34VOICE 7
+#define REG_DASLOTCONF3_DAI7TOADO3 5
+
+/* REG_DASLOTCONF4 */
+#define REG_DASLOTCONF4_DAI8TOADO4 5
+
+/* REG_DASLOTCONF5 */
+#define REG_DASLOTCONF5_DA56VOICE 7
+#define REG_DASLOTCONF5_DAI7TOADO5 5
+
+/* REG_DASLOTCONF6 */
+#define REG_DASLOTCONF6_DAI8TOADO6 5
+
+/* REG_DASLOTCONF7 */
+#define REG_DASLOTCONF7_DAI8TOADO7 5
+
+/* REG_DASLOTCONF8 */
+#define REG_DASLOTCONF8_DAI7TOADO8 5
+
+#define REG_DASLOTCONFX_SLTODAX4 4
+#define REG_DASLOTCONFX_SLTODAX3 3
+#define REG_DASLOTCONFX_SLTODAX2 2
+#define REG_DASLOTCONFX_SLTODAX1 1
+#define REG_DASLOTCONFX_SLTODAX0 0
+
+/* REG_CLASSDCONF1 */
+#define REG_CLASSDCONF1_PARLHF 7
+#define REG_CLASSDCONF1_PARLVIB 6
+#define REG_CLASSDCONF1_VIB1SWAPEN 3
+#define REG_CLASSDCONF1_VIB2SWAPEN 2
+#define REG_CLASSDCONF1_HFLSWAPEN 1
+#define REG_CLASSDCONF1_HFRSWAPEN 0
+
+/* REG_CLASSDCONF2 */
+#define REG_CLASSDCONF2_FIRBYP3 7
+#define REG_CLASSDCONF2_FIRBYP2 6
+#define REG_CLASSDCONF2_FIRBYP1 5
+#define REG_CLASSDCONF2_FIRBYP0 4
+#define REG_CLASSDCONF2_HIGHVOLEN3 3
+#define REG_CLASSDCONF2_HIGHVOLEN2 2
+#define REG_CLASSDCONF2_HIGHVOLEN1 1
+#define REG_CLASSDCONF2_HIGHVOLEN0 0
+
+/* REG_CLASSDCONF3 */
+#define REG_CLASSDCONF3_DITHHPGAIN 4
+#define REG_CLASSDCONF3_DITHHPGAIN_MAX 0x0A
+#define REG_CLASSDCONF3_DITHWGAIN 0
+#define REG_CLASSDCONF3_DITHWGAIN_MAX 0x0A
+
+/* REG_DMICFILTCONF */
+#define REG_DMICFILTCONF_ANCINSEL 7
+#define REG_DMICFILTCONF_DMIC1SINC3 5
+#define REG_DMICFILTCONF_DMIC2SINC3 4
+#define REG_DMICFILTCONF_DMIC3SINC3 3
+#define REG_DMICFILTCONF_DMIC4SINC3 2
+#define REG_DMICFILTCONF_DMIC5SINC3 1
+#define REG_DMICFILTCONF_DMIC6SINC3 0
+
+/* REG_DIGMULTCONF1 */
+#define REG_DIGMULTCONF1_DATOHSLEN 7
+#define REG_DIGMULTCONF1_DATOHSREN 6
+#define REG_DIGMULTCONF1_AD1SEL 5
+#define REG_DIGMULTCONF1_AD2SEL 4
+#define REG_DIGMULTCONF1_AD3SEL 3
+#define REG_DIGMULTCONF1_AD5SEL 2
+#define REG_DIGMULTCONF1_AD6SEL 1
+#define REG_DIGMULTCONF1_ANCSEL 0
+
+/* REG_DIGMULTCONF2 */
+#define REG_DIGMULTCONF2_DATOHFREN 7
+#define REG_DIGMULTCONF2_DATOHFLEN 6
+#define REG_DIGMULTCONF2_HFRSEL 5
+#define REG_DIGMULTCONF2_HFLSEL 4
+#define REG_DIGMULTCONF2_FIRSID1SEL 2
+#define REG_DIGMULTCONF2_FIRSID2SEL 0
+
+/* REG_ADDIGGAIN1 */
+/* REG_ADDIGGAIN2 */
+/* REG_ADDIGGAIN3 */
+/* REG_ADDIGGAIN4 */
+/* REG_ADDIGGAIN5 */
+/* REG_ADDIGGAIN6 */
+#define REG_ADDIGGAINX_FADEDISADX 6
+#define REG_ADDIGGAINX_ADXGAIN_MAX 0x3F
+
+/* REG_DADIGGAIN1 */
+/* REG_DADIGGAIN2 */
+/* REG_DADIGGAIN3 */
+/* REG_DADIGGAIN4 */
+/* REG_DADIGGAIN5 */
+/* REG_DADIGGAIN6 */
+#define REG_DADIGGAINX_FADEDISDAX 6
+#define REG_DADIGGAINX_DAXGAIN_MAX 0x3F
+
+/* REG_ADDIGLOOPGAIN1 */
+/* REG_ADDIGLOOPGAIN2 */
+#define REG_ADDIGLOOPGAINX_FADEDISADXL 6
+#define REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX 0x3F
+
+/* REG_HSLEARDIGGAIN */
+#define REG_HSLEARDIGGAIN_HSSINC1 7
+#define REG_HSLEARDIGGAIN_FADEDISHSL 4
+#define REG_HSLEARDIGGAIN_HSLDGAIN_MAX 0x09
+
+/* REG_HSRDIGGAIN */
+#define REG_HSRDIGGAIN_FADESPEED 6
+#define REG_HSRDIGGAIN_FADEDISHSR 4
+#define REG_HSRDIGGAIN_HSRDGAIN_MAX 0x09
+
+/* REG_SIDFIRGAIN1 */
+/* REG_SIDFIRGAIN2 */
+#define REG_SIDFIRGAINX_FIRSIDXGAIN_MAX 0x1F
+
+/* REG_ANCCONF1 */
+/* REG_ANCCONF2 */
+/* REG_ANCCONF3 */
+/* REG_ANCCONF4 */
+/* REG_ANCCONF5 */
+/* REG_ANCCONF6 */
+/* REG_ANCCONF7 */
+/* REG_ANCCONF8 */
+/* REG_ANCCONF9 */
+/* REG_ANCCONF10 */
+/* REG_ANCCONF11 */
+/* REG_ANCCONF12 */
+/* REG_ANCCONF13 */
+/* REG_ANCCONF14 */
+/* REG_SIDFIRADR */
+/* REG_SIDFIRCOEF1 */
+/* REG_SIDFIRCOEF2 */
+/* REG_SIDFIRCONF */
+/* REG_AUDINTMASK1 */
+/* REG_AUDINTSOURCE1 */
+/* REG_AUDINTMASK2 */
+/* REG_AUDINTSOURCE2 */
+/* REG_FIFOCONF1 */
+/* REG_FIFOCONF2 */
+/* REG_FIFOCONF3 */
+/* REG_FIFOCONF4 */
+/* REG_FIFOCONF5 */
+/* REG_FIFOCONF6 */
+/* REG_AUDREV */
+
+#endif
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig
index a5cdf4358b5..9463ff264fd 100644
--- a/sound/soc/ux500/Kconfig
+++ b/sound/soc/ux500/Kconfig
@@ -25,6 +25,14 @@ config SND_SOC_UX500_AB5500
help
Say Y if you want to include the AB5500 codec.
+config SND_SOC_UX500_AB8500
+ bool "Codec - AB8500"
+ depends on SND_SOC_UX500 && UX500_SOC_DB8500 && AB8500_CORE && AB8500_GPADC
+ select SND_SOC_AB8500
+ default n
+ help
+ Say Y if you want to include AB8500 audio codec.
+
config SND_SOC_UX500_CG29XX
bool "Codec - CG29xx"
depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && MFD_CG2900_AUDIO
diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile
index 84e642bf193..30799a030de 100644
--- a/sound/soc/ux500/Makefile
+++ b/sound/soc/ux500/Makefile
@@ -3,7 +3,10 @@
ifdef CONFIG_SND_SOC_UX500_DEBUG
CFLAGS_ux500_pcm.o := -DDEBUG
CFLAGS_ux500_msp_dai.o := -DDEBUG
+CFLAGS_ux500_ab3550.o := -DDEBUG
+CFLAGS_ux500_ab8500.o := -DDEBUG
CFLAGS_ux500_av8100.o := -DDEBUG
+CFLAGS_ux500_cg29xx.o := -DDEBUG
endif
ifdef CONFIG_SND_SOC_UX500_AB3550
@@ -16,6 +19,11 @@ snd-soc-ux500-AB5500-objs := ux500_ab5500.o
obj-$(CONFIG_SND_SOC_UX500_AB5500) += ux500_ab5500.o
endif
+ifdef CONFIG_SND_SOC_UX500_AB8500
+snd-soc-ux500-ab8500-objs := ux500_ab8500.o
+obj-$(CONFIG_SND_SOC_UX500_AB8500) += ux500_ab8500_accessory.o ux500_ab8500.o
+endif
+
ifdef CONFIG_SND_SOC_UX500_AV8100
snd-soc-ux500-av8100-objs := ux500_av8100.o
obj-$(CONFIG_SND_SOC_UX500_AV8100) += ux500_av8100.o
diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c
index 54fb9a1584e..e4208951821 100644
--- a/sound/soc/ux500/u8500.c
+++ b/sound/soc/ux500/u8500.c
@@ -24,6 +24,10 @@
#include "ux500_ab3550.h"
#endif
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+#include "ux500_ab8500.h"
+#endif
+
#ifdef CONFIG_SND_SOC_UX500_AV8100
#include "ux500_av8100.h"
#endif
@@ -101,6 +105,28 @@ struct snd_soc_dai_link u8500_dai_links[] = {
.ops = ux500_ab3550_ops,
},
#endif
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ {
+ .name = "ab8500_0",
+ .stream_name = "ab8500_0",
+ .cpu_dai_name = "i2s.1",
+ .codec_dai_name = "ab8500-codec-dai.0",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab8500-codec.0",
+ .init = ux500_ab8500_machine_codec_init,
+ .ops = ux500_ab8500_ops,
+ },
+ {
+ .name = "ab8500_1",
+ .stream_name = "ab8500_1",
+ .cpu_dai_name = "i2s.3",
+ .codec_dai_name = "ab8500-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab8500-codec.0",
+ .init = NULL,
+ .ops = ux500_ab8500_ops,
+ },
+ #endif
#ifdef CONFIG_SND_SOC_UX500_CG29XX
{
.name = "cg29xx_0",
@@ -124,7 +150,7 @@ static struct snd_soc_card u8500_drvdata = {
static int __init u8500_soc_init(void)
{
- int ret = 0;
+ int ret;
pr_debug("%s: Enter.\n", __func__);
@@ -140,6 +166,15 @@ static int __init u8500_soc_init(void)
platform_device_register(&cg29xx_codec);
#endif
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ pr_debug("%s: Calling init-function for AB8500 machine driver.\n",
+ __func__);
+ ret = ux500_ab8500_soc_machine_drv_init();
+ if (ret)
+ pr_err("%s: ux500_ab8500_soc_machine_drv_init failed (%d).\n",
+ __func__, ret);
+ #endif
+
pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n",
__func__);
platform_device_register(&ux500_pcm);
@@ -187,6 +222,12 @@ static void __exit u8500_soc_exit(void)
{
pr_debug("%s: Enter.\n", __func__);
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ pr_debug("%s: Calling exit-function for AB8500 machine driver.\n",
+ __func__);
+ ux500_ab8500_soc_machine_drv_cleanup();
+ #endif
+
pr_debug("%s: Unregister platform device (%s).\n",
__func__,
u8500_drvdata.name);
diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c
new file mode 100644
index 00000000000..d651f88aa55
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>,
+ * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * 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/device.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <mach/hardware.h>
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "../codecs/ab8500.h"
+#include "ux500_ab8500_accessory.h"
+
+#define AB8500_DAIFMT_TDM_MASTER \
+ (SND_SOC_DAIFMT_DSP_B | \
+ SND_SOC_DAIFMT_CBM_CFM | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CONT)
+
+#define TX_SLOT_MONO 0x0008
+#define TX_SLOT_STEREO 0x000a
+#define RX_SLOT_MONO 0x0001
+#define RX_SLOT_STEREO 0x0003
+
+#define DEF_TX_SLOTS TX_SLOT_STEREO
+#define DEF_RX_SLOTS RX_SLOT_MONO
+
+/* Slot configuration */
+static unsigned int tx_slots = DEF_TX_SLOTS;
+static unsigned int rx_slots = DEF_RX_SLOTS;
+
+/* List the regulators that are to be controlled.. */
+static struct regulator_bulk_data ab8500_regus[4] = {
+ { .supply = "v-dmic" },
+ { .supply = "v-audio" },
+ { .supply = "v-amic1" },
+ { .supply = "v-amic2" }
+};
+
+int ux500_ab8500_startup(struct snd_pcm_substream *substream)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+void ux500_ab8500_shutdown(struct snd_pcm_substream *substream)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ /* Reset slots configuration to default(s) */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tx_slots = DEF_TX_SLOTS;
+ else
+ rx_slots = DEF_RX_SLOTS;
+}
+
+int ux500_ab8500_hw_params(
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int channels, ret = 0;
+
+ pr_info("%s: Enter\n", __func__);
+
+ pr_info("%s: substream->pcm->name = %s\n"
+ "substream->pcm->id = %s.\n"
+ "substream->name = %s.\n"
+ "substream->number = %d.\n",
+ __func__,
+ substream->pcm->name,
+ substream->pcm->id,
+ substream->name,
+ substream->number);
+
+ ret = snd_soc_dai_set_fmt(codec_dai, AB8500_DAIFMT_TDM_MASTER);
+ if (ret < 0) {
+ pr_err("%s: snd_soc_dai_set_fmt failed codec_dai %d.\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, AB8500_DAIFMT_TDM_MASTER);
+ if (ret < 0) {
+ pr_err("%s: snd_soc_dai_set_fmt cpu_dai %d.\n",
+ __func__, ret);
+ return ret;
+ }
+
+ channels = params_channels(params);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (channels == 1)
+ tx_slots = TX_SLOT_MONO;
+ else if (channels == 2)
+ tx_slots = TX_SLOT_STEREO;
+ else
+ return -EINVAL;
+ } else {
+ if (channels == 1)
+ rx_slots = RX_SLOT_MONO;
+ else if (channels == 2)
+ rx_slots = RX_SLOT_STEREO;
+ else
+ return -EINVAL;
+ }
+
+ pr_info("%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n",
+ __func__, tx_slots, rx_slots);
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ tx_slots, rx_slots,
+ 16, 16);
+
+ pr_info("%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n",
+ __func__, tx_slots, rx_slots);
+ ret += snd_soc_dai_set_tdm_slot(codec_dai,
+ tx_slots, rx_slots,
+ 16, 16);
+
+ return ret;
+}
+
+static int create_regulators(void)
+{
+ int i, status = 0;
+
+ pr_info("%s: Enter.\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i)
+ ab8500_regus[i].consumer = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) {
+ ab8500_regus[i].consumer = regulator_get(NULL,
+ ab8500_regus[i].supply);
+ if (IS_ERR(ab8500_regus[i].consumer)) {
+ status = PTR_ERR(ab8500_regus[i].consumer);
+ pr_err("%s: Failed to get supply '%s' (%d)\n",
+ __func__, ab8500_regus[i].supply, status);
+ ab8500_regus[i].consumer = NULL;
+ goto err_get;
+ }
+ }
+
+ return 0;
+
+err_get:
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) {
+ if (ab8500_regus[i].consumer) {
+ regulator_put(ab8500_regus[i].consumer);
+ ab8500_regus[i].consumer = NULL;
+ }
+ }
+
+ return status;
+}
+
+static int enable_regulator(const char *name)
+{
+ int i, status;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) {
+ if (strcmp(name, ab8500_regus[i].supply) != 0)
+ continue;
+
+ status = regulator_enable(ab8500_regus[i].consumer);
+
+ if (status != 0) {
+ pr_err("%s: Failure with regulator %s (%d)\n",
+ __func__, name, status);
+ return status;
+ } else {
+ pr_info("%s: Enabled regulator %s.\n",
+ __func__, name);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void disable_regulator(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) {
+ if (strcmp(name, ab8500_regus[i].supply) == 0) {
+ regulator_disable(ab8500_regus[i].consumer);
+ return;
+ }
+ }
+}
+
+int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ pr_info("%s Enter.\n", __func__);
+
+ /*
+ int status = 0;
+ status = ab8500_accessory_init(rtd->codec);
+ if (status < 0) {
+ pr_err("%s: Failed to initialize accessories (%d).\n",
+ __func__, status);
+ return status;
+ }
+ */
+
+ /* TODO: Add required DAPM routes to control regulators on demand */
+
+ return 0;
+}
+
+int ux500_ab8500_soc_machine_drv_init(void)
+{
+ int i;
+ int status = 0;
+
+ pr_info("%s: Enter.\n", __func__);
+
+ status = create_regulators();
+ if (status < 0) {
+ pr_err("%s: Failed to instantiate regulators (%d).\n",
+ __func__, status);
+ return status;
+ }
+
+ /* TODO: Enable only regulators really needed at this point.. */
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i)
+ status += enable_regulator(ab8500_regus[i].supply);
+
+ return status;
+}
+
+void ux500_ab8500_soc_machine_drv_cleanup(void)
+{
+ int i;
+
+ pr_info("%s: Enter.\n", __func__);
+
+ /*
+ ab8500_accessory_cleanup();
+ */
+
+ /* Roll out all the regulators */
+ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i)
+ disable_regulator(ab8500_regus[i].supply);
+ regulator_bulk_free(ARRAY_SIZE(ab8500_regus), ab8500_regus);
+}
+
+struct snd_soc_ops ux500_ab8500_ops[] = {
+ {
+ .hw_params = ux500_ab8500_hw_params,
+ .startup = ux500_ab8500_startup,
+ .shutdown = ux500_ab8500_shutdown,
+ }
+};
diff --git a/sound/soc/ux500/ux500_ab8500.h b/sound/soc/ux500/ux500_ab8500.h
new file mode 100644
index 00000000000..058ea8c4ef2
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef UX500_AB8500_H
+#define UX500_AB8500_H
+
+extern struct snd_soc_ops ux500_ab8500_ops[];
+
+struct snd_soc_pcm_runtime;
+
+int ux500_ab8500_startup(struct snd_pcm_substream *substream);
+
+void ux500_ab8500_shutdown(struct snd_pcm_substream *substream);
+
+int ux500_ab8500_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+
+int ux500_ab8500_soc_machine_drv_init(void);
+
+void ux500_ab8500_soc_machine_drv_cleanup(void);
+
+int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *runtime);
+
+#endif
diff --git a/sound/soc/ux500/ux500_ab8500_accessory.c b/sound/soc/ux500/ux500_ab8500_accessory.c
new file mode 100644
index 00000000000..ab022144a1f
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500_accessory.c
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.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> /* Needed by all modules */
+#include <linux/kernel.h> /* Needed for KERN_INFO */
+#include <linux/init.h> /* Needed for the macros */
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/mfd/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/ab8500/ab8500-gpadc.h>
+
+/* Local definitions -----------------------------------------------*/
+#define ARG_USED(x) ((void)x)
+
+/* How many times, in a row, same device type is to be evaluate in order
+ to accept it. Also limits for configuration validation. */
+#define REQUIRED_DET_MIN 2
+#define REQUIRED_DET_MAX 10
+#define REQUIRED_DET_DEF 4
+
+/* Unique value used to identify Headset button input device */
+#define BTN_INPUT_UNIQUE_VALUE "AB8500HsBtn"
+#define BTN_INPUT_DEV_NAME "Headset button"
+
+/* Timeout (ms) after jack type is checked after plug-in irq is received */
+#define DEBOUNCE_PLUG_EVENT_MS 100
+/* Timeout (ms) for subsequent plug checks used to make sure connected device
+ is really detected properly */
+#define DEBOUNCE_PLUG_RETEST_MS 50
+/* Timeout after jack disconnect status is checked after plug-out det.*/
+#define DEBOUNCE_UNPLUG_EVENT_MS 250
+
+/*
+* Register definition for accessory detection.
+*/
+#define AB8500_REGU_CTRL1_SPARE_REG 0x0384
+#define AB8500_ECI_CTRL_REG 0x0800
+#define AB8500_ECI_HOOKLEVEL_REG 0x0801
+#define AB8500_ACC_DET_DB1_REG 0x0880
+#define AB8500_ACC_DET_DB2_REG 0x0881
+#define AB8500_ACC_DET_CTRL_REG 0x0882
+#define AB8500_IT_SOURCE5_REG 0x0E04
+#define AB8500_GPIO_PUD5_REG 0x1034
+#define AB8500_GPIO_DIR5_REG 0x1014
+
+/* REGISTER: AB8500_ACC_DET_CTRL_REG */
+#define BIT_ACCDETCTRL_22_ENA 0x20
+#define BIT_ACCDETCTRL_21_ENA 0x10
+#define BIT_ACCDETCTRL_2PU_ENA 0x08
+#define BIT_ACCDETCTRL_2PUS_ENA 0x02
+#define BIT_ACCDETCTRL_1_ENA 0x01
+
+/* REGISTER: AB8500_GPIO_DIR5_REG */
+#define BIT_GPIO35_DIR_OUT 0x04
+
+/* REGISTER: AB8500_REGU_CTRL1_SPARE_REG */
+#define BIT_REGUCTRL1SPARE_VAMIC1_GROUND 0x01
+
+/* REGISTER: AB8500_IT_SOURCE5_REG */
+#define BIT_ITSOURCE5_ACCDET1 0x04
+
+/* REGISTER: AB8500_ACC_DET_DB1_REG
+*
+* Accdetect1 debounce time limits, in milliseconds
+*/
+#define ACCDETECT1_DB_MIN 0
+#define ACCDETECT1_DB_MAX 70
+#define ACCDETECT1_DB_DEF 60
+#define MASK_ACCDETECT1_DB 0x07
+
+/* REGISTER: AB8500_ACC_DET_DB2_REG
+* Accdetect1 threshold voltage limits, in millivolts */
+#define ACCDETECT1_TH_MIN 300
+#define ACCDETECT1_TH_MAX 1800
+#define ACCDETECT1_TH_DEF 1800
+#define MASK_ACCDETECT1_TH 0x78
+/* Accdetect21 threshold voltage limits, in millivolts */
+#define ACCDETECT21_TH_MIN 300
+#define ACCDETECT21_TH_MAX 1800
+#define ACCDETECT21_TH_DEF 1000
+#define MASK_ACCDETECT21_TH 0x0F
+/* Accdetect22 threshold voltage limits, in millivolts */
+#define ACCDETECT22_TH_MIN 300
+#define ACCDETECT22_TH_MAX 1800
+#define ACCDETECT22_TH_DEF 1000
+#define MASK_ACCDETECT22_TH 0xF0
+
+/* After being loaded, how fast the first check is to be made */
+#define INIT_DELAY_MS 5000
+
+/* Name of the workqueue thread */
+#define WORKQUEUE_NAME "ab8500_av_wq"
+
+/* Voltage limits (mV) for various types of AV Accessories */
+#define ACCESSORY_DET_VOL_DONTCARE -1
+#define ACCESSORY_HEADPHONE_DET_VOL_MIN 0
+#define ACCESSORY_HEADPHONE_DET_VOL_MAX 40
+#define ACCESSORY_CVIDEO_DET_VOL_MIN 41
+#define ACCESSORY_CVIDEO_DET_VOL_MAX 105
+#define ACCESSORY_CARKIT_DET_VOL_MIN 1100
+#define ACCESSORY_CARKIT_DET_VOL_MAX 1300
+#define ACCESSORY_HEADSET_DET_VOL_MIN 0
+#define ACCESSORY_HEADSET_DET_VOL_MAX 200
+#define ACCESSORY_OPENCABLE_DET_VOL_MIN 1730
+#define ACCESSORY_OPENCABLE_DET_VOL_MAX 2150
+
+/* Macros ----------------------------------------------------------*/
+
+/*
+* Conviniency macros to check jack characteristics.
+*/
+#define jack_supports_mic(type) \
+ (type == JACK_TYPE_HEADSET || type == JACK_TYPE_CARKIT)
+#define jack_supports_spkr(type) \
+ ((type != JACK_DISCONNECTED) && (type != JACK_CONNECTED))
+#define jack_supports_buttons(type) \
+ ((type == JACK_TYPE_HEADSET) ||\
+ (type == JACK_TYPE_CARKIT) ||\
+ (type == JACK_TYPE_OPENCABLE))
+
+/* Enumerations -----------------------------------------------------*/
+
+/* Possible states of a "standard" accessory button.. */
+enum accessory_button_state {
+ /* Uninitialized */
+ ACCESSORY_BUTTON_UNSPECIFIED,
+ /* Button is currently pressed down */
+ ACCESSORY_BUTTON_PRESSED,
+ /* Button is not currently pressed down */
+ ACCESSORY_BUTTON_RELEASED
+};
+
+/* Two separate accessory detection inputs, one to detect
+* plugin/plugout events and other to detect button events
+* while plugged in
+*/
+enum accessory_detect_channel {
+ ACCESSORY_DETECT_CHANNEL_1 = 1,
+ ACCESSORY_DETECT_CHANNEL_2 = 2,
+ ACCESSORY_DETECT_CHANNEL_ALL = 3
+};
+
+/* Regulators used in accessory detection */
+enum accessory_regulator {
+ ACCESSORY_REGULATOR_VAUDIO = 1,
+ ACCESSORY_REGULATOR_VAMIC1 = 2,
+ ACCESSORY_REGULATOR_ALL = 3
+};
+
+/* State of the jack and possible type */
+enum accessory_jack_state {
+ JACK_UNSPECIFIED,
+ JACK_DISCONNECTED,
+ JACK_CONNECTED,
+ JACK_TYPE_HEADPHONE,
+ JACK_TYPE_HEADSET,
+ JACK_TYPE_CARKIT,
+ JACK_TYPE_OPENCABLE,
+ JACK_TYPE_CVIDEO,
+ JACK_TYPE_ECI
+};
+
+/* Accessory detect operations enumerated */
+enum accessory_op {
+ ACCESSORY_TEST_DISCONNECTED,
+ ACCESSORY_TEST_CONNECTED,
+ ACCESSORY_TEST_HEADPHONE,
+ ACCESSORY_TEST_ECI,
+ ACCESSORY_TEST_CVIDEO,
+ ACCESSORY_TEST_OPENCABLE,
+ ACCESSORY_TEST_CARKIT,
+ ACCESSORY_TEST_HEADSET
+};
+
+/*
+* @E_PLUG_IRQ
+* @E_UNPLUG_IRQ
+* @E_BUTTON_PRESS_IRQ
+* @E_BUTTON_RELEASE_IRQ
+*/
+enum accessory_irq {
+ E_PLUG_IRQ,
+ E_UNPLUG_IRQ,
+ E_BUTTON_PRESS_IRQ,
+ E_BUTTON_RELEASE_IRQ
+};
+
+/*
+* @irq Interrupt enumeration
+* @name Name of the interrupt as defined by the core driver.
+* @handler Interrupt handler
+* @registered flag indicating whether this particular interrupt
+* is already registered or not.
+*/
+struct accessory_irq_descriptor {
+ enum accessory_irq irq;
+ const char *name;
+ irq_handler_t handler;
+ int registered;
+};
+
+/*
+* Maps a detect operation to accessory state
+* @operation
+* @jack_state
+* @meas_mv
+* @minvol
+* @maxvol
+*/
+struct accessory_op_to_jack_state {
+ /* Operation to be performed */
+ enum accessory_op operation;
+ /* If operation evals to true -> state is set to mentioned */
+ enum accessory_jack_state jack_state;
+ /* Whether mic voltage should be remeasured during this step,
+ if not set, the previously measured cached value is to be used
+ when making the decision */
+ int meas_mv;
+ /* Voltage limits to make the decision */
+ int minvol;
+ int maxvol;
+};
+
+/*
+* Device data, capsulates all relevant device data structures.
+*/
+struct devdata {
+
+ struct ab8500 *ab8500;
+
+ struct platform_device *codec_p_dev;
+
+ /* Codec device for register access etc. */
+ struct device *codec_dev;
+
+ struct snd_soc_jack jack;
+
+ /* Worker thread for accessory detection purposes */
+ struct workqueue_struct *irq_work_queue;
+
+ /* Input device for button events */
+ struct input_dev *btn_input_dev;
+
+ /* Current plug status */
+ enum accessory_jack_state jack_type;
+
+ /* Indeed, we are checking the jack status x times in a row before
+ trusting the results.. due the bouncing that occurs during plugin */
+ int jack_det_count;
+ enum accessory_jack_state jack_type_temp;
+
+ /* Current state of the accessory button if any */
+ enum accessory_button_state btn_state;
+
+ /* threshold value for accdetect1 */
+ u8 accdetect1th;
+ /* debounce value for accdetect1 */
+ u8 accdetect1db;
+ /* threshold value for accdetect21 */
+ u8 accdetect21th;
+ /* threshold value for accdetect22 */
+ u8 accdetect22th;
+ /* How many detections requred in a row to accept */
+ int required_det;
+ /* Vamic1 regulator */
+ struct regulator *vamic1_reg;
+ /* Is vamic1 regulator currently held or not */
+ int vamic1_reg_enabled;
+ /* VAudio regulator */
+ struct regulator *vaudio_reg;
+ /* Is vaudio regulator currently held or not */
+ int vaudio_reg_enabled;
+};
+
+/* Forward declarations -------------------------------------------------*/
+
+static irqreturn_t unplug_irq_handler(int irq, void *_userdata);
+static irqreturn_t plug_irq_handler(int irq, void *_userdata);
+
+static void config_accdetect(enum accessory_jack_state state);
+static void release_irq(enum accessory_irq irq_id);
+static void claim_irq(enum accessory_irq irq_id);
+
+static void unplug_irq_handler_work(struct work_struct *work);
+static void plug_irq_handler_work(struct work_struct *work);
+static void deferred_init_handler_work(struct work_struct *work);
+static enum accessory_jack_state detect(void);
+static u8 ab8500_reg_read(u8 bank, u32 reg);
+
+/* Local variables ----------------------------------------------------------*/
+DECLARE_DELAYED_WORK(plug_irq_work, plug_irq_handler_work);
+DECLARE_DELAYED_WORK(unplug_irq_work, unplug_irq_handler_work);
+DECLARE_DELAYED_WORK(deferred_init_work, deferred_init_handler_work);
+
+/* Device data - dynamically allocated during the init. */
+static struct devdata *devdata;
+
+/* Note, order of these detections actually count -> changing
+the order might actually cause inproper detection results.
+*/
+static struct accessory_op_to_jack_state detect_ops[] = {
+ /* Check is the PLUG connected */
+ {
+ ACCESSORY_TEST_DISCONNECTED,
+ JACK_DISCONNECTED,
+ 1,
+ ACCESSORY_DET_VOL_DONTCARE,
+ ACCESSORY_DET_VOL_DONTCARE
+ },
+ /* Check is the type HEADPHONE ( no mic ) */
+ {
+ ACCESSORY_TEST_HEADPHONE,
+ JACK_TYPE_HEADPHONE,
+ 1,
+ ACCESSORY_HEADPHONE_DET_VOL_MIN,
+ ACCESSORY_HEADPHONE_DET_VOL_MAX
+ },
+ /* Check with ECI communication whether device is present or not */
+ {
+ ACCESSORY_TEST_ECI,
+ JACK_TYPE_ECI,
+ 0,
+ ACCESSORY_DET_VOL_DONTCARE,
+ ACCESSORY_DET_VOL_DONTCARE
+ },
+ /* Check is the VIDEOCABLE connected */
+ {
+ ACCESSORY_TEST_CVIDEO,
+ JACK_TYPE_CVIDEO,
+ 0,
+ ACCESSORY_CVIDEO_DET_VOL_MIN,
+ ACCESSORY_CVIDEO_DET_VOL_MAX
+ },
+ /* Check is the OPEN CABLE CONNECTED */
+ {
+ ACCESSORY_TEST_OPENCABLE,
+ JACK_TYPE_OPENCABLE,
+ 0,
+ ACCESSORY_OPENCABLE_DET_VOL_MIN,
+ ACCESSORY_OPENCABLE_DET_VOL_MAX
+ },
+ /* Check is the CARKIT connected */
+ {
+ ACCESSORY_TEST_CARKIT,
+ JACK_TYPE_CARKIT,
+ 1,
+ ACCESSORY_CARKIT_DET_VOL_MIN,
+ ACCESSORY_CARKIT_DET_VOL_MAX
+ },
+ /* Check is HEADSET connected */
+ {
+ ACCESSORY_TEST_HEADSET,
+ JACK_TYPE_HEADSET,
+ 0,
+ ACCESSORY_HEADSET_DET_VOL_MIN,
+ ACCESSORY_HEADSET_DET_VOL_MAX
+ },
+ /* Last but not least, check is some unsupported device connected */
+ {
+ ACCESSORY_TEST_CONNECTED,
+ JACK_CONNECTED,
+ 0,
+ ACCESSORY_DET_VOL_DONTCARE,
+ ACCESSORY_DET_VOL_DONTCARE
+ }
+};
+
+#ifdef DEBUG
+#define dump_reg(txt_pre, txt_post, bank, reg)\
+{\
+ u8 val = ab8500_reg_read(bank, reg);\
+ printk(KERN_INFO " R(%s%s) = %02x\n", txt_pre, txt_post, val);\
+}
+
+void dump_regs(const char *txt)
+{
+#if 0
+ dump_reg("383", " ACCESSORY", 3, 0x383);
+ dump_reg("880", " ACCESSORY", 8, 0x880);
+ dump_reg("881", " ACCESSORY", 8, 0x881);
+ dump_reg("882", " ACCESSORY", 8, 0x882);
+ dump_reg("e44", " ACCESSORY", 14, 0xE44);
+#endif
+}
+#endif
+
+static const char *accessory_str(enum accessory_jack_state status)
+{
+ const char *ret;
+
+ switch (status) {
+ case JACK_DISCONNECTED:
+ ret = "DISCONNECTED";
+ break;
+ case JACK_CONNECTED:
+ ret = "CONNECTED";
+ break;
+ case JACK_TYPE_HEADPHONE:
+ ret = "HEADPHONE";
+ break;
+ case JACK_TYPE_HEADSET:
+ ret = "HEADSET";
+ break;
+ case JACK_TYPE_CARKIT:
+ ret = "CARKIT";
+ break;
+ case JACK_TYPE_OPENCABLE:
+ ret = "OPENCABLE";
+ break;
+ case JACK_TYPE_CVIDEO:
+ ret = "CVIDEO";
+ break;
+ case JACK_TYPE_ECI:
+ ret = "ECI";
+ break;
+ default:
+ ret = "ERROR";
+ }
+
+ return ret;
+}
+
+/*
+* Enables specific accessory detection regulator intelligently so
+* that reference counts are taken into account.
+*/
+static void accessory_regulator_enable(enum accessory_regulator reg)
+{
+ if (reg & ACCESSORY_REGULATOR_VAUDIO) {
+ if (!devdata->vaudio_reg_enabled) {
+ if (!regulator_enable(devdata->vaudio_reg))
+ devdata->vaudio_reg_enabled = 1;
+ }
+ }
+
+ if (reg & ACCESSORY_REGULATOR_VAMIC1) {
+ if (!devdata->vamic1_reg_enabled) {
+ if (!regulator_enable(devdata->vamic1_reg))
+ devdata->vamic1_reg_enabled = 1;
+ }
+ }
+}
+
+/*
+* Disables specific accessory detection related regulator intelligently so
+* that reference counts are taken into account.
+*/
+static void accessory_regulator_disable(enum accessory_regulator reg)
+{
+ if (reg & ACCESSORY_REGULATOR_VAUDIO) {
+ if (devdata->vaudio_reg_enabled) {
+ if (!regulator_disable(devdata->vaudio_reg))
+ devdata->vaudio_reg_enabled = 0;
+ }
+ }
+
+ if (reg & ACCESSORY_REGULATOR_VAMIC1) {
+ if (devdata->vamic1_reg_enabled) {
+ if (!regulator_disable(devdata->vamic1_reg))
+ devdata->vamic1_reg_enabled = 0;
+ }
+ }
+}
+static u8 ab8500_reg_read(u8 bank, u32 adr);
+
+static int ab8500_reg_write(u8 bank, u32 adr, u8 data)
+{
+ int status = abx500_set_register_interruptible(
+ devdata->codec_dev,
+ bank,
+ adr & 0xFF,
+ data);
+
+ if (status < 0)
+ pr_err("%s: %x failed: %d\n", __func__, adr, status);
+
+ return status;
+}
+
+/* Generic AB8500 register reading
+ */
+static u8 ab8500_reg_read(u8 bank, u32 adr)
+{
+ u8 value = 0;
+ int status = abx500_get_register_interruptible(
+ devdata->codec_dev,
+ bank,
+ adr & 0xFF,
+ &value);
+ if (status < 0)
+ pr_err("%s: %x failed: %d\n", __func__, adr, status);
+
+ return value;
+}
+
+/*
+* ab8500_set_bits - reads value, applies bits and writes back
+*/
+static void ab8500_set_bits(u8 bank, u32 reg, u8 bits)
+{
+ u8 value = ab8500_reg_read(bank, reg);
+
+ /* Check do we actually need to set any bits */
+ if ((value & bits) == bits)
+ return;
+ value |= bits;
+ ab8500_reg_write(bank, reg, value);
+}
+
+/*
+* Clears the certain bits ( mask given ) from given register in given bank
+*/
+static void ab8500_clr_bits(u8 bank, u32 reg, u8 bits)
+{
+ u8 value = ab8500_reg_read(bank, reg);
+
+ /* Check do we actually need to clear any bits */
+ if ((value & bits) == 0)
+ return;
+ value &= ~bits;
+ ab8500_reg_write(bank, reg, value);
+}
+
+/*
+* Configures HW so that accessory detection input 2 is effective
+*/
+static void config_accdetect2_hw(int enable)
+{
+ if (enable) {
+ ab8500_reg_write(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_DB2_REG,
+ devdata->accdetect21th |
+ devdata->accdetect22th);
+
+ ab8500_set_bits(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BIT_ACCDETCTRL_21_ENA |
+ BIT_ACCDETCTRL_2PU_ENA);
+
+ accessory_regulator_enable(ACCESSORY_REGULATOR_ALL);
+
+ ab8500_set_bits(AB8500_ECI_AV_ACC,
+ AB8500_ECI_CTRL_REG,
+ 0x3a);
+ /* @TODO: Check clearing this later on. */
+ ab8500_reg_write(AB8500_ECI_AV_ACC,
+ AB8500_ECI_HOOKLEVEL_REG,
+ 0x1f);
+ } else {
+ ab8500_reg_write(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_DB2_REG,
+ devdata->accdetect21th |
+ devdata->accdetect22th);
+
+ ab8500_clr_bits(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BIT_ACCDETCTRL_21_ENA |
+ BIT_ACCDETCTRL_2PU_ENA);
+
+ accessory_regulator_disable(ACCESSORY_REGULATOR_ALL);
+
+ /* Set ECI bits accordingly.. NOTE: reg 800+801 are UNDOC. */
+ ab8500_clr_bits(AB8500_ECI_AV_ACC,
+ AB8500_ECI_CTRL_REG,
+ 0x3a);
+ }
+}
+
+/*
+* config_accdetect1 - configures accessory detection 1 hardware in order
+* to be able to recognized plug-in/plug-out events.
+*/
+void config_accdetect1_hw(int enable)
+{
+ if (enable) {
+ ab8500_reg_write(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_DB1_REG,
+ devdata->accdetect1th | devdata->accdetect1db);
+
+ /* enable accdetect1 comparator */
+ ab8500_set_bits(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BIT_ACCDETCTRL_1_ENA |
+ BIT_ACCDETCTRL_2PUS_ENA);
+ } else {
+ /* Disable accdetect1 comparator */
+ ab8500_clr_bits(AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BIT_ACCDETCTRL_1_ENA |
+ BIT_ACCDETCTRL_2PUS_ENA);
+ }
+}
+
+/*
+* Configures hardware so that it is possible to receive
+* "standard" button presses from headset/carkit accessories.
+*/
+static void start_wait_button_events(void)
+{
+ int stat;
+
+ if (devdata && !devdata->btn_input_dev) {
+ devdata->btn_input_dev = input_allocate_device();
+ if (!devdata->btn_input_dev) {
+ pr_err("%s: Failed to allocate input.\n", __func__);
+ goto out;
+ }
+ input_set_capability(devdata->btn_input_dev,
+ EV_KEY,
+ KEY_PHONE);
+
+ devdata->btn_input_dev->name = BTN_INPUT_DEV_NAME;
+ devdata->btn_input_dev->uniq = BTN_INPUT_UNIQUE_VALUE;
+
+ stat = input_register_device(devdata->btn_input_dev);
+ if (stat) {
+ pr_err("%s: register_input_device: %d\n",
+ __func__,
+ stat);
+ input_free_device(devdata->btn_input_dev);
+ devdata->btn_input_dev = NULL;
+ }
+ }
+out:
+ return;
+}
+
+/*
+* stop_wait_button_events - stops waiting for button events.
+*/
+static void stop_wait_button_events(void)
+{
+ if (devdata && devdata->btn_input_dev) {
+ input_unregister_device(devdata->btn_input_dev);
+ devdata->btn_input_dev = NULL;
+ }
+}
+
+int init_jackdev(struct snd_soc_codec *codec)
+{
+ int status;
+ pr_info("%s: Enter\n", __func__);
+
+ status = snd_soc_jack_new(codec,
+ "Headset status",
+ SND_JACK_HEADPHONE |
+ SND_JACK_MICROPHONE |
+ SND_JACK_HEADSET |
+ SND_JACK_LINEOUT |
+ SND_JACK_MECHANICAL |
+ SND_JACK_VIDEOOUT,
+ &devdata->jack);
+
+ pr_info("%s: snd_soc_jack_new = %d\n", __func__, status);
+
+ return status;
+}
+
+void update_jack_status(enum accessory_jack_state jack_status)
+{
+ /* We can use "full" mask as we maintain the jack state by ourselves */
+ const int mask = 0xFF;
+ int value = 0;
+
+ if (!devdata || !devdata->jack.jack)
+ return;
+
+ if (jack_status != JACK_DISCONNECTED && jack_status != JACK_UNSPECIFIED)
+ value |= SND_JACK_MECHANICAL;
+ if (jack_supports_mic(jack_status))
+ value |= SND_JACK_MICROPHONE;
+ if (jack_supports_spkr(jack_status))
+ value |= (SND_JACK_HEADPHONE | SND_JACK_LINEOUT);
+ if (jack_status == JACK_TYPE_CVIDEO)
+ value |= SND_JACK_VIDEOOUT;
+
+ snd_soc_jack_report(&devdata->jack, value, mask);
+}
+
+/*
+* Returns the mic line voltage in millivolts.
+*/
+static int meas_voltage(u8 input)
+{
+ int raw_vol = ab8500_gpadc_convert(devdata->ab8500->gpadc, input);
+
+ return (raw_vol * 2500)/1023;
+}
+
+/*
+* Returns 1 if accessories are plugged in, 0 if not.
+*/
+static int detect_plugged_in(void)
+{
+ u8 val = ab8500_reg_read(AB8500_INTERRUPT, AB8500_IT_SOURCE5_REG);
+
+ if (val & BIT_ITSOURCE5_ACCDET1)
+ return 0;
+ return 1;
+}
+
+/*
+* mic_line_voltage_stable - measures a relative stable voltage (dV/dT)
+* from mic1 input.
+*/
+static int meas_voltage_stable(u8 input)
+{
+ int iterations = 3;
+ const int sleepms = 20;
+ const int dmax = 30;
+ int v1, v2, dv;
+
+ v1 = meas_voltage(input);
+ do {
+ msleep(sleepms);
+ --iterations;
+ v2 = meas_voltage(input);
+ dv = abs(v2 - v1);
+ v1 = v2;
+ } while (iterations > 0 && dv > dmax);
+
+ return v1;
+}
+
+/*
+* unplug_irq_handler_work - worked routine for unplug interrupt.
+* run in context of the worker thread.
+*/
+static void unplug_irq_handler_work(struct work_struct *work)
+{
+ enum accessory_jack_state type;
+
+ pr_info("%s: Enter\n", __func__);
+
+ type = detect();
+
+ if (type == JACK_DISCONNECTED) {
+ /* If disconnected > do reset the state accordingly */
+ config_accdetect(type);
+ devdata->btn_state = ACCESSORY_BUTTON_UNSPECIFIED;
+ stop_wait_button_events();
+ }
+
+ devdata->jack_type = type;
+ devdata->jack_det_count = 0;
+
+ /* Tell the userland what has just happened */
+ update_jack_status(devdata->jack_type);
+
+ pr_info("Accessory: %s\n", accessory_str(devdata->jack_type));
+}
+
+/*
+* unplug_irq_handler - interrupt handler for unplug event.
+*/
+static irqreturn_t unplug_irq_handler(int irq, void *_userdata)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ ARG_USED(irq);
+ ARG_USED(_userdata);
+ queue_delayed_work(devdata->irq_work_queue,
+ &unplug_irq_work,
+ msecs_to_jiffies(DEBOUNCE_UNPLUG_EVENT_MS));
+
+ return IRQ_HANDLED;
+}
+
+/*
+* plug_irq_handler - Interrupt handler routing for plugging in the cable.
+*/
+static irqreturn_t plug_irq_handler(int irq, void *_userdata)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ ARG_USED(irq);
+ ARG_USED(_userdata);
+ queue_delayed_work(devdata->irq_work_queue,
+ &plug_irq_work,
+ msecs_to_jiffies(DEBOUNCE_PLUG_EVENT_MS));
+
+ return IRQ_HANDLED;
+}
+
+/*
+* plug_irq_handler_work - work processing for plug irq. Run in context
+* of the worker thread.
+*/
+static void plug_irq_handler_work(struct work_struct *work)
+{
+ enum accessory_jack_state type;
+
+ pr_info("%s: Enter\n", __func__);
+
+ type = detect();
+
+ if (devdata->jack_type_temp == type) {
+ devdata->jack_det_count++;
+ } else {
+ devdata->jack_det_count = 1;
+ devdata->jack_type_temp = type;
+ }
+
+ if (devdata->jack_det_count < devdata->required_det) {
+ /* Perform the detection again until we are certain.. */
+ /* @TODO: Should we have some kind of "total" limit in order
+ not to go into loop in case different type is
+ detected all the time. Not very likely though.*/
+ queue_delayed_work(devdata->irq_work_queue,
+ &plug_irq_work,
+ msecs_to_jiffies(DEBOUNCE_PLUG_RETEST_MS));
+ } else {
+ pr_info("Accessory: %s\n", accessory_str(type));
+
+ if (jack_supports_buttons(type) && type != JACK_TYPE_OPENCABLE)
+ start_wait_button_events();
+
+ /* Report status only if we were disconnected previously, also
+ if opencable is detected, it is not reported sep.
+ just yet */
+ if (devdata->jack_type == JACK_DISCONNECTED) {
+ devdata->jack_type = type;
+ if (devdata->jack_type != JACK_TYPE_OPENCABLE)
+ update_jack_status(devdata->jack_type);
+ }
+ config_accdetect(devdata->jack_type);
+ }
+}
+
+static void report_btn_event(int pressed)
+{
+ if (devdata->btn_input_dev) {
+ input_report_key(devdata->btn_input_dev, KEY_PHONE, pressed);
+ input_sync(devdata->btn_input_dev);
+ pr_info("HS-BUTTON: %s\n", pressed ? "PRESSED" : "RELEASED");
+ }
+}
+
+/*
+* button_press_irq_handler - irq handler when headset button press is detected.
+*/
+static irqreturn_t button_press_irq_handler(int irq, void *_userdata)
+{
+ pr_info("%s: Enter\n", __func__);
+ ARG_USED(_userdata);
+
+ if (devdata->jack_type == JACK_TYPE_OPENCABLE) {
+ /* Simulate the scenario where plug would have
+ been connected... */
+ plug_irq_handler(irq, _userdata);
+ return IRQ_HANDLED;
+ } else if (devdata->btn_state == ACCESSORY_BUTTON_RELEASED &&
+ jack_supports_buttons(devdata->jack_type)) {
+ report_btn_event(1);
+ }
+
+ /* Update button state */
+ if (devdata->jack_type == JACK_DISCONNECTED)
+ devdata->btn_state = ACCESSORY_BUTTON_UNSPECIFIED;
+ else
+ devdata->btn_state = ACCESSORY_BUTTON_PRESSED;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t button_release_irq_handler(int irq, void *_userdata)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ ARG_USED(_userdata);
+ ARG_USED(irq);
+
+ /* Handle button presses only of headset and/or carkit */
+ if (devdata->btn_state == ACCESSORY_BUTTON_PRESSED &&
+ (devdata->jack_type == JACK_TYPE_HEADSET ||
+ devdata->jack_type == JACK_TYPE_CARKIT)) {
+ report_btn_event(0);
+ }
+ if (devdata->jack_type == JACK_DISCONNECTED)
+ devdata->btn_state = ACCESSORY_BUTTON_UNSPECIFIED;
+ else
+ devdata->btn_state = ACCESSORY_BUTTON_RELEASED;
+
+ return IRQ_HANDLED;
+}
+
+/*
+* config_hw_plug_connected - configures the hardware after plug
+* insertion has been detected. That is necessary in order to be
+* able to detect what type of plug-has been inserted.
+*/
+static void config_hw_test_plug_connected(int enable)
+{
+ if (enable) {
+ ab8500_set_bits(AB8500_MISC, AB8500_GPIO_PUD5_REG, 0x04);
+ accessory_regulator_enable(ACCESSORY_REGULATOR_VAMIC1);
+ } else {
+ ab8500_clr_bits(AB8500_MISC, AB8500_GPIO_PUD5_REG, 0x04);
+ accessory_regulator_disable(ACCESSORY_REGULATOR_VAMIC1);
+ }
+}
+
+/*
+* config_hw_basic_carkit - configures AB8500 so that carkit detection can
+* be made
+*/
+static void config_hw_test_basic_carkit(int enable)
+{
+ if (enable) {
+ /* Disable mic bias that is mandatory for further detections*/
+ accessory_regulator_disable(ACCESSORY_REGULATOR_VAMIC1);
+
+ /* Ground the VAMic1 output when disabled ->
+ zero input provided */
+ ab8500_set_bits(AB8500_REGU_CTRL1,
+ AB8500_REGU_CTRL1_SPARE_REG,
+ BIT_REGUCTRL1SPARE_VAMIC1_GROUND);
+ } else {
+ /* NOTE: Here we do not re-enable the VAMic1 Regulator. */
+
+ /* Un-Ground the VAMic1 output when enabled */
+ ab8500_clr_bits(AB8500_REGU_CTRL1,
+ AB8500_REGU_CTRL1_SPARE_REG,
+ BIT_REGUCTRL1SPARE_VAMIC1_GROUND);
+ }
+}
+
+/*
+* mic_vol_in_range - measures somewhat stable mic1 voltage and returns it.
+* Uses cached value if not explicitly requested not to do so ( force_read ).
+*/
+static int mic_vol_in_range(int lo, int hi, int force_read)
+{
+ static int mv = -100;
+
+ if (mv == -100 || force_read)
+ mv = meas_voltage_stable(ACC_DETECT2);
+
+#ifdef DEBUG_VOLTAGE
+ pr_info("mic: %dmV (l=%dmV h=%dmV)\n", mv, lo, hi);
+#endif
+ return (mv >= lo && mv <= hi) ? 1 : 0;
+}
+
+/*
+* detect_hw - tries to detect specific type of connected hardware
+*/
+static int detect_hw(struct accessory_op_to_jack_state *op)
+{
+ int status;
+
+ switch (op->operation) {
+ case ACCESSORY_TEST_DISCONNECTED:
+ config_hw_test_plug_connected(1);
+ status = !detect_plugged_in();
+ break;
+ case ACCESSORY_TEST_CONNECTED:
+ config_hw_test_plug_connected(1);
+ status = detect_plugged_in();
+ break;
+ case ACCESSORY_TEST_ECI:
+ /* @TODO: Integrate ECI support here */
+ status = 0;
+ break;
+ case ACCESSORY_TEST_HEADPHONE:
+ case ACCESSORY_TEST_CVIDEO:
+ case ACCESSORY_TEST_CARKIT:
+ case ACCESSORY_TEST_HEADSET:
+ case ACCESSORY_TEST_OPENCABLE:
+ /* Only do the config when testing carkit, does not harm for
+ others but is unncessary anyway. */
+ if (op->operation == ACCESSORY_TEST_CARKIT)
+ config_hw_test_basic_carkit(1);
+ status = mic_vol_in_range(op->minvol, op->maxvol, op->meas_mv);
+ break;
+ default:
+ status = 0;
+ }
+
+ return status;
+}
+
+/*
+* detect - detects the type of the currently connected accessory if any.
+*/
+static enum accessory_jack_state detect()
+{
+ enum accessory_jack_state status = JACK_DISCONNECTED;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(detect_ops); ++i) {
+ if (detect_hw(&detect_ops[i])) {
+ status = detect_ops[i].jack_state;
+ break;
+ }
+ }
+
+ config_hw_test_basic_carkit(0);
+ config_hw_test_plug_connected(0);
+
+ return status;
+}
+
+static struct accessory_irq_descriptor irq_descriptors[] = {
+ {E_PLUG_IRQ, "ACC_DETECT_1DB_F",
+ plug_irq_handler, 0},
+ {E_UNPLUG_IRQ, "ACC_DETECT_1DB_R",
+ unplug_irq_handler, 0},
+ {E_BUTTON_PRESS_IRQ, "ACC_DETECT_22DB_F",
+ button_press_irq_handler, 0},
+ {E_BUTTON_RELEASE_IRQ, "ACC_DETECT_22DB_R",
+ button_release_irq_handler, 0},
+};
+
+/*
+* Claims access to specific IRQ.
+*/
+static void claim_irq(enum accessory_irq irq_id)
+{
+ int ret;
+ int irq;
+
+ if (irq_descriptors[irq_id].registered)
+ return;
+
+ irq = platform_get_irq_byname(
+ devdata->codec_p_dev,
+ irq_descriptors[irq_id].name);
+ if (irq < 0) {
+ pr_err("%s: failed to get irq nbr %s\n", __func__,
+ irq_descriptors[irq_id].name);
+ return;
+ }
+
+ ret = request_threaded_irq(irq,
+ NULL,
+ irq_descriptors[irq_id].handler,
+ 0,
+ irq_descriptors[irq_id].name,
+ devdata);
+ if (ret != 0) {
+ pr_err("%s: Failed to claim irq %s (%d)\n",
+ __func__,
+ irq_descriptors[irq_id].name,
+ ret);
+ } else {
+ irq_descriptors[irq_id].registered = 1;
+ pr_info("%s: EnableIRQ %s\n",
+ __func__, irq_descriptors[irq_id].name);
+ }
+}
+
+/*
+* Reclaims access to specific IRQ.
+*/
+static void release_irq(enum accessory_irq irq_id)
+{
+ int irq;
+
+ if (!irq_descriptors[irq_id].registered)
+ return;
+
+ irq = platform_get_irq_byname(
+ devdata->codec_p_dev,
+ irq_descriptors[irq_id].name);
+ if (irq < 0) {
+ pr_err("%s: failed to get irq %s\n", __func__,
+ irq_descriptors[irq_id].name);
+ } else {
+ free_irq(irq, devdata);
+ irq_descriptors[irq_id].registered = 0;
+ pr_info("%s: DisableIRQ %s\n",
+ __func__, irq_descriptors[irq_id].name);
+ }
+}
+
+static void config_accdetect_hw(enum accessory_jack_state state)
+{
+ switch (state) {
+ case JACK_UNSPECIFIED:
+ config_accdetect1_hw(1);
+ config_accdetect2_hw(0);
+ break;
+
+ case JACK_DISCONNECTED:
+ case JACK_CONNECTED:
+ case JACK_TYPE_HEADPHONE:
+ case JACK_TYPE_CVIDEO:
+ case JACK_TYPE_ECI:
+ /* Plug In/Out detection possible */
+ config_accdetect1_hw(1);
+ config_accdetect2_hw(0);
+ break;
+
+ case JACK_TYPE_HEADSET:
+ case JACK_TYPE_CARKIT:
+ case JACK_TYPE_OPENCABLE:
+ /* Plug Out + Button Press/Release possible */
+ config_accdetect1_hw(1);
+ config_accdetect2_hw(1);
+ break;
+
+ default:
+ pr_err("%s: Unspecified state: %d\n", __func__, state);
+ }
+}
+
+static void config_accdetect_irq(enum accessory_jack_state state)
+{
+ switch (state) {
+ case JACK_UNSPECIFIED:
+ release_irq(E_PLUG_IRQ);
+ release_irq(E_UNPLUG_IRQ);
+ release_irq(E_BUTTON_PRESS_IRQ);
+ release_irq(E_BUTTON_RELEASE_IRQ);
+ break;
+
+ case JACK_DISCONNECTED:
+ release_irq(E_BUTTON_PRESS_IRQ);
+ release_irq(E_BUTTON_RELEASE_IRQ);
+ claim_irq(E_UNPLUG_IRQ);
+ claim_irq(E_PLUG_IRQ);
+ break;
+
+ case JACK_CONNECTED:
+ case JACK_TYPE_HEADPHONE:
+ case JACK_TYPE_CVIDEO:
+ case JACK_TYPE_ECI:
+ release_irq(E_BUTTON_PRESS_IRQ);
+ release_irq(E_BUTTON_RELEASE_IRQ);
+ claim_irq(E_PLUG_IRQ);
+ claim_irq(E_UNPLUG_IRQ);
+ break;
+
+ case JACK_TYPE_HEADSET:
+ case JACK_TYPE_CARKIT:
+ case JACK_TYPE_OPENCABLE:
+ release_irq(E_PLUG_IRQ);
+ claim_irq(E_UNPLUG_IRQ);
+ claim_irq(E_BUTTON_PRESS_IRQ);
+ claim_irq(E_BUTTON_RELEASE_IRQ);
+ break;
+
+ default:
+ pr_err("%s: Unspecified state: %d\n", __func__, state);
+ }
+}
+
+static void config_accdetect(enum accessory_jack_state state)
+{
+ config_accdetect_hw(state);
+ config_accdetect_irq(state);
+}
+
+static int ab8500_accessory_configure(int accdetect1db,
+ int accdetect1th,
+ int accdetect21th,
+ int accdetect22th,
+ int detections)
+{
+ if (!devdata)
+ return -1;
+
+ if (accdetect1db >= ACCDETECT1_DB_MIN &&
+ accdetect1db <= ACCDETECT1_DB_MAX) {
+ devdata->accdetect1db =
+ (accdetect1db / 10) & MASK_ACCDETECT1_DB;
+ pr_info("%s: AccDetect1DB: %dmV > %02x\n", __func__,
+ accdetect1db, devdata->accdetect1db);
+ }
+ if (accdetect1th >= ACCDETECT1_TH_MIN &&
+ accdetect1th <= ACCDETECT1_TH_MAX) {
+ devdata->accdetect1th =
+ ((accdetect1th / 100 - 3) << 3) & MASK_ACCDETECT1_TH;
+ pr_info("%s: AccDetect1TH: %dmV > %02x\n", __func__,
+ accdetect1th, devdata->accdetect1th);
+ }
+ if (accdetect21th >= ACCDETECT21_TH_MIN &&
+ accdetect21th <= ACCDETECT21_TH_MAX) {
+ devdata->accdetect21th =
+ (accdetect21th / 100 - 3) & MASK_ACCDETECT21_TH;
+ pr_info("%s: AccDetect21TH: %dmV > %02x\n", __func__,
+ accdetect21th, devdata->accdetect21th);
+ }
+ if (accdetect22th >= ACCDETECT22_TH_MIN &&
+ accdetect22th <= ACCDETECT22_TH_MAX) {
+ devdata->accdetect22th =
+ ((accdetect22th / 100 - 3) << 4) & MASK_ACCDETECT22_TH;
+ pr_info("%s: AccDetect22TH: %dmV > %02x\n", __func__,
+ accdetect22th, devdata->accdetect22th);
+ }
+ if (detections >= REQUIRED_DET_MIN && detections <= REQUIRED_DET_MAX) {
+ devdata->required_det = detections;
+ pr_info("%s: detections_required: %d\n", __func__, detections);
+ }
+
+ return 0;
+}
+
+/*
+* Deferred initialization of the work.
+*/
+static void deferred_init_handler_work(struct work_struct *work)
+{
+ pr_info("%s: Enter\n", __func__);
+
+ ab8500_set_bits(AB8500_MISC, AB8500_GPIO_DIR5_REG, BIT_GPIO35_DIR_OUT);
+ config_accdetect(JACK_UNSPECIFIED);
+ plug_irq_handler_work(work);
+}
+
+static int init_platform_devices(struct snd_soc_codec *codec)
+{
+ devdata->ab8500 = dev_get_drvdata(codec->dev->parent);
+ if (!devdata->ab8500 || !devdata->ab8500->gpadc) {
+ pr_err("%s: failed to get ab8500 plat. driver\n", __func__);
+ return -ENOENT;
+ }
+ devdata->codec_dev = codec->dev;
+ devdata->codec_p_dev = to_platform_device(codec->dev);
+
+ return 0;
+}
+
+int ab8500_accessory_init(struct snd_soc_codec *codec)
+{
+ pr_info("Enter: %s\n", __func__);
+
+ if (devdata)
+ return 0;
+
+ if (!codec)
+ goto fail_invalid_args;
+
+ devdata = kzalloc(sizeof(struct devdata), GFP_KERNEL);
+ if (!devdata) {
+ pr_err("%s: Memory allocation failed\n", __func__);
+ goto fail_no_mem_for_devdata;
+ }
+
+ /* Get vamic1 regulator */
+ devdata->vamic1_reg = regulator_get(NULL, "v-amic1");
+ if (IS_ERR(devdata->vamic1_reg)) {
+ pr_err("%s: Failed to allocate regulator vamic1\n", __func__);
+ devdata->vamic1_reg = NULL;
+ goto fail_no_vamic1_regulator;
+ }
+
+ /* Get vaudio regulator */
+ devdata->vaudio_reg = regulator_get(NULL, "v-audio");
+ if (IS_ERR(devdata->vaudio_reg)) {
+ pr_err("%s: Failed to allocate regulator vaudio\n", __func__);
+ devdata->vaudio_reg = NULL;
+ goto fail_no_vaudio_regulator;
+ }
+
+ /* Configure some default values that will always work somehow */
+ ab8500_accessory_configure(
+ ACCDETECT1_DB_DEF,
+ ACCDETECT1_TH_DEF,
+ ACCDETECT21_TH_DEF,
+ ACCDETECT22_TH_DEF,
+ REQUIRED_DET_DEF);
+
+ if (init_jackdev(codec) < 0) {
+ pr_err("%s: Failed to init jack input device\n", __func__);
+ goto fail_no_jackdev;
+ }
+
+ if (init_platform_devices(codec) < 0) {
+ pr_err("%s: platform dev failed\n", __func__);
+ goto fail_no_platform;
+ }
+
+ devdata->btn_state = ACCESSORY_BUTTON_UNSPECIFIED;
+ devdata->jack_type_temp = devdata->jack_type = JACK_DISCONNECTED;
+ devdata->irq_work_queue = create_singlethread_workqueue(WORKQUEUE_NAME);
+ if (!devdata->irq_work_queue) {
+ pr_err("%s: Failed to create work queue\n", __func__);
+ goto fail_no_mem_for_wq;
+ }
+
+ /* Perform deferred initialization. */
+ queue_delayed_work(devdata->irq_work_queue,
+ &deferred_init_work,
+ msecs_to_jiffies(INIT_DELAY_MS));
+
+ return 0;
+
+fail_no_mem_for_wq:
+fail_no_platform:
+fail_no_jackdev:
+ regulator_put(devdata->vaudio_reg);
+fail_no_vaudio_regulator:
+ regulator_put(devdata->vamic1_reg);
+fail_no_vamic1_regulator:
+ kfree(devdata);
+ devdata = NULL;
+fail_no_mem_for_devdata:
+fail_invalid_args:
+ return -ENOMEM;
+}
+
+void ab8500_accessory_cleanup(void)
+{
+ pr_info("Enter: %s\n", __func__);
+
+ if (!devdata)
+ return;
+
+ config_accdetect(JACK_UNSPECIFIED);
+
+ stop_wait_button_events();
+
+ cancel_delayed_work(&plug_irq_work);
+ cancel_delayed_work(&unplug_irq_work);
+ cancel_delayed_work(&deferred_init_work);
+
+ if (devdata->vamic1_reg)
+ regulator_put(devdata->vamic1_reg);
+ if (devdata->vaudio_reg)
+ regulator_put(devdata->vaudio_reg);
+ if (devdata->irq_work_queue) {
+ flush_workqueue(devdata->irq_work_queue);
+ destroy_workqueue(devdata->irq_work_queue);
+ }
+
+ kfree(devdata);
+ devdata = NULL;
+}
diff --git a/sound/soc/ux500/ux500_ab8500_accessory.h b/sound/soc/ux500/ux500_ab8500_accessory.h
new file mode 100644
index 00000000000..e019cd840aa
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500_accessory.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef UX500_AB8500_ACCESSORY_H__
+#define UX500_AB8500_ACCESSORY_H__
+
+struct snd_soc_codec;
+
+extern int ab8500_accessory_init(struct snd_soc_codec *codec);
+extern void ab8500_accessory_cleanup(void);
+
+#endif