diff options
author | Mikko Sarmanne <mikko.sarmanne@symbio.com> | 2010-12-15 08:43:08 +0200 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2011-12-06 10:59:28 +0100 |
commit | 968b4a8ca3664ee4d2032fa30ba309e7d81e2717 (patch) | |
tree | 5ab1ba69705b0da3cb552dc649f2d33273955a12 /sound | |
parent | 073384cf5d5f49e9b5e0c64ff7b6fd1f71702ddc (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/Kconfig | 4 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 3 | ||||
-rw-r--r-- | sound/soc/codecs/ab8500.c | 1714 | ||||
-rw-r--r-- | sound/soc/codecs/ab8500.h | 484 | ||||
-rw-r--r-- | sound/soc/ux500/Kconfig | 8 | ||||
-rw-r--r-- | sound/soc/ux500/Makefile | 8 | ||||
-rw-r--r-- | sound/soc/ux500/u8500.c | 43 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab8500.c | 275 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab8500.h | 34 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab8500_accessory.c | 1349 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab8500_accessory.h | 22 |
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 |