ASoC: Add support for TWL4030 audio codec
authorSteve Sakoman <steve@sakoman.com>
Fri, 31 Oct 2008 04:35:26 +0000 (21:35 -0700)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Fri, 31 Oct 2008 12:33:27 +0000 (12:33 +0000)
Signed-off-by: Steve Sakoman <steve@sakoman.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/twl4030.c [new file with mode: 0644]
sound/soc/codecs/twl4030.h [new file with mode: 0644]

index 0fd3341e6e3b1454e7c2d03c3f17ed656e92e7d0..b73c36aad677f9aa11f5d9ae6465e90afec0283a 100644 (file)
@@ -9,6 +9,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_TLV320AIC23 if I2C
        select SND_SOC_TLV320AIC26 if SPI_MASTER
        select SND_SOC_TLV320AIC3X if I2C
+       select SND_SOC_TWL4030 if TWL4030_CORE
        select SND_SOC_UDA1380 if I2C
        select SND_SOC_WM8510 if (I2C || SPI_MASTER)
        select SND_SOC_WM8580 if I2C
@@ -79,6 +80,10 @@ config SND_SOC_TLV320AIC3X
        tristate
        depends on I2C
 
+config SND_SOC_TWL4030
+       tristate
+       depends on TWL4030_CORE
+
 config SND_SOC_UDA1380
         tristate
 
index 90f0a585fc70e1f5a4d9a9be1b8514a22100917a..3b9b58a0ea7d14dc18b162cd2a1c8d6bd7efc63d 100644 (file)
@@ -7,6 +7,7 @@ snd-soc-ssm2602-objs := ssm2602.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic26-objs := tlv320aic26.o
 snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-twl4030-objs := twl4030.o
 snd-soc-uda1380-objs := uda1380.o
 snd-soc-wm8510-objs := wm8510.o
 snd-soc-wm8580-objs := wm8580.o
@@ -29,6 +30,7 @@ obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)      += snd-soc-tlv320aic23.o
 obj-$(CONFIG_SND_SOC_TLV320AIC26)      += snd-soc-tlv320aic26.o
 obj-$(CONFIG_SND_SOC_TLV320AIC3X)      += snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_TWL4030)  += snd-soc-twl4030.o
 obj-$(CONFIG_SND_SOC_UDA1380)  += snd-soc-uda1380.o
 obj-$(CONFIG_SND_SOC_WM8510)   += snd-soc-wm8510.o
 obj-$(CONFIG_SND_SOC_WM8580)   += snd-soc-wm8580.o
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
new file mode 100644 (file)
index 0000000..ee2f0d3
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * ALSA SoC TWL4030 codec driver
+ *
+ * Author:      Steve Sakoman, <steve@sakoman.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "twl4030.h"
+
+/*
+ * twl4030 register cache & default register settings
+ */
+static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
+       0x00, /* this register not used         */
+       0x93, /* REG_CODEC_MODE         (0x1)   */
+       0xc3, /* REG_OPTION             (0x2)   */
+       0x00, /* REG_UNKNOWN            (0x3)   */
+       0x00, /* REG_MICBIAS_CTL        (0x4)   */
+       0x24, /* REG_ANAMICL            (0x5)   */
+       0x04, /* REG_ANAMICR            (0x6)   */
+       0x0a, /* REG_AVADC_CTL          (0x7)   */
+       0x00, /* REG_ADCMICSEL          (0x8)   */
+       0x00, /* REG_DIGMIXING          (0x9)   */
+       0x0c, /* REG_ATXL1PGA           (0xA)   */
+       0x0c, /* REG_ATXR1PGA           (0xB)   */
+       0x00, /* REG_AVTXL2PGA          (0xC)   */
+       0x00, /* REG_AVTXR2PGA          (0xD)   */
+       0x01, /* REG_AUDIO_IF           (0xE)   */
+       0x00, /* REG_VOICE_IF           (0xF)   */
+       0x00, /* REG_ARXR1PGA           (0x10)  */
+       0x00, /* REG_ARXL1PGA           (0x11)  */
+       0x6c, /* REG_ARXR2PGA           (0x12)  */
+       0x6c, /* REG_ARXL2PGA           (0x13)  */
+       0x00, /* REG_VRXPGA             (0x14)  */
+       0x00, /* REG_VSTPGA             (0x15)  */
+       0x00, /* REG_VRX2ARXPGA         (0x16)  */
+       0x0c, /* REG_AVDAC_CTL          (0x17)  */
+       0x00, /* REG_ARX2VTXPGA         (0x18)  */
+       0x00, /* REG_ARXL1_APGA_CTL     (0x19)  */
+       0x00, /* REG_ARXR1_APGA_CTL     (0x1A)  */
+       0x4b, /* REG_ARXL2_APGA_CTL     (0x1B)  */
+       0x4b, /* REG_ARXR2_APGA_CTL     (0x1C)  */
+       0x00, /* REG_ATX2ARXPGA         (0x1D)  */
+       0x00, /* REG_BT_IF              (0x1E)  */
+       0x00, /* REG_BTPGA              (0x1F)  */
+       0x00, /* REG_BTSTPGA            (0x20)  */
+       0x00, /* REG_EAR_CTL            (0x21)  */
+       0x24, /* REG_HS_SEL             (0x22)  */
+       0x0a, /* REG_HS_GAIN_SET        (0x23)  */
+       0x00, /* REG_HS_POPN_SET        (0x24)  */
+       0x00, /* REG_PREDL_CTL          (0x25)  */
+       0x00, /* REG_PREDR_CTL          (0x26)  */
+       0x00, /* REG_PRECKL_CTL         (0x27)  */
+       0x00, /* REG_PRECKR_CTL         (0x28)  */
+       0x00, /* REG_HFL_CTL            (0x29)  */
+       0x00, /* REG_HFR_CTL            (0x2A)  */
+       0x00, /* REG_ALC_CTL            (0x2B)  */
+       0x00, /* REG_ALC_SET1           (0x2C)  */
+       0x00, /* REG_ALC_SET2           (0x2D)  */
+       0x00, /* REG_BOOST_CTL          (0x2E)  */
+       0x01, /* REG_SOFTVOL_CTL        (0x2F)  */
+       0x00, /* REG_DTMF_FREQSEL       (0x30)  */
+       0x00, /* REG_DTMF_TONEXT1H      (0x31)  */
+       0x00, /* REG_DTMF_TONEXT1L      (0x32)  */
+       0x00, /* REG_DTMF_TONEXT2H      (0x33)  */
+       0x00, /* REG_DTMF_TONEXT2L      (0x34)  */
+       0x00, /* REG_DTMF_TONOFF        (0x35)  */
+       0x00, /* REG_DTMF_WANONOFF      (0x36)  */
+       0x00, /* REG_I2S_RX_SCRAMBLE_H  (0x37)  */
+       0x00, /* REG_I2S_RX_SCRAMBLE_M  (0x38)  */
+       0x00, /* REG_I2S_RX_SCRAMBLE_L  (0x39)  */
+       0x16, /* REG_APLL_CTL           (0x3A)  */
+       0x00, /* REG_DTMF_CTL           (0x3B)  */
+       0x00, /* REG_DTMF_PGA_CTL2      (0x3C)  */
+       0x00, /* REG_DTMF_PGA_CTL1      (0x3D)  */
+       0x00, /* REG_MISC_SET_1         (0x3E)  */
+       0x00, /* REG_PCMBTMUX           (0x3F)  */
+       0x00, /* not used               (0x40)  */
+       0x00, /* not used               (0x41)  */
+       0x00, /* not used               (0x42)  */
+       0x00, /* REG_RX_PATH_SEL        (0x43)  */
+       0x00, /* REG_VDL_APGA_CTL       (0x44)  */
+       0x00, /* REG_VIBRA_CTL          (0x45)  */
+       0x00, /* REG_VIBRA_SET          (0x46)  */
+       0x00, /* REG_VIBRA_PWM_SET      (0x47)  */
+       0x00, /* REG_ANAMIC_GAIN        (0x48)  */
+       0x00, /* REG_MISC_SET_2         (0x49)  */
+};
+
+/*
+ * read twl4030 register cache
+ */
+static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec,
+       unsigned int reg)
+{
+       u8 *cache = codec->reg_cache;
+
+       return cache[reg];
+}
+
+/*
+ * write twl4030 register cache
+ */
+static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec,
+                                               u8 reg, u8 value)
+{
+       u8 *cache = codec->reg_cache;
+
+       if (reg >= TWL4030_CACHEREGNUM)
+               return;
+       cache[reg] = value;
+}
+
+/*
+ * write to the twl4030 register space
+ */
+static int twl4030_write(struct snd_soc_codec *codec,
+                       unsigned int reg, unsigned int value)
+{
+       twl4030_write_reg_cache(codec, reg, value);
+       return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
+}
+
+static void twl4030_clear_codecpdz(struct snd_soc_codec *codec)
+{
+       u8 mode;
+
+       mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
+       twl4030_write(codec, TWL4030_REG_CODEC_MODE,
+               mode & ~TWL4030_CODECPDZ);
+
+       /* REVISIT: this delay is present in TI sample drivers */
+       /* but there seems to be no TRM requirement for it     */
+       udelay(10);
+}
+
+static void twl4030_set_codecpdz(struct snd_soc_codec *codec)
+{
+       u8 mode;
+
+       mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
+       twl4030_write(codec, TWL4030_REG_CODEC_MODE,
+               mode | TWL4030_CODECPDZ);
+
+       /* REVISIT: this delay is present in TI sample drivers */
+       /* but there seems to be no TRM requirement for it     */
+       udelay(10);
+}
+
+static void twl4030_init_chip(struct snd_soc_codec *codec)
+{
+       int i;
+
+       /* clear CODECPDZ prior to setting register defaults */
+       twl4030_clear_codecpdz(codec);
+
+       /* set all audio section registers to reasonable defaults */
+       for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
+               twl4030_write(codec, i, twl4030_reg[i]);
+
+}
+
+static const struct snd_kcontrol_new twl4030_snd_controls[] = {
+       SOC_DOUBLE_R("Master Playback Volume",
+                TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
+               0, 127, 0),
+       SOC_DOUBLE_R("Capture Volume",
+                TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA,
+               0, 127, 0),
+};
+
+/* add non dapm controls */
+static int twl4030_add_controls(struct snd_soc_codec *codec)
+{
+       int err, i;
+
+       for (i = 0; i < ARRAY_SIZE(twl4030_snd_controls); i++) {
+               err = snd_ctl_add(codec->card,
+                                 snd_soc_cnew(&twl4030_snd_controls[i],
+                                               codec, NULL));
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
+       SND_SOC_DAPM_INPUT("INL"),
+       SND_SOC_DAPM_INPUT("INR"),
+
+       SND_SOC_DAPM_OUTPUT("OUTL"),
+       SND_SOC_DAPM_OUTPUT("OUTR"),
+
+       SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0),
+       SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0),
+
+       SND_SOC_DAPM_ADC("ADCL", "Left Capture", SND_SOC_NOPM, 0, 0),
+       SND_SOC_DAPM_ADC("ADCR", "Right Capture", SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+       /* outputs */
+       {"OUTL", NULL, "DACL"},
+       {"OUTR", NULL, "DACR"},
+
+       /* inputs */
+       {"ADCL", NULL, "INL"},
+       {"ADCR", NULL, "INR"},
+};
+
+static int twl4030_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, twl4030_dapm_widgets,
+                                ARRAY_SIZE(twl4030_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+static void twl4030_power_up(struct snd_soc_codec *codec)
+{
+       u8 anamicl, regmisc1, byte, popn, hsgain;
+       int i = 0;
+
+       /* set CODECPDZ to turn on codec */
+       twl4030_set_codecpdz(codec);
+
+       /* initiate offset cancellation */
+       anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+       twl4030_write(codec, TWL4030_REG_ANAMICL,
+               anamicl | TWL4030_CNCL_OFFSET_START);
+
+       /* wait for offset cancellation to complete */
+       do {
+               /* this takes a little while, so don't slam i2c */
+               udelay(2000);
+               twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+                                   TWL4030_REG_ANAMICL);
+       } while ((i++ < 100) &&
+                ((byte & TWL4030_CNCL_OFFSET_START) ==
+                 TWL4030_CNCL_OFFSET_START));
+
+       /* anti-pop when changing analog gain */
+       regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
+       twl4030_write(codec, TWL4030_REG_MISC_SET_1,
+               regmisc1 | TWL4030_SMOOTH_ANAVOL_EN);
+
+       /* toggle CODECPDZ as per TRM */
+       twl4030_clear_codecpdz(codec);
+       twl4030_set_codecpdz(codec);
+
+       /* program anti-pop with bias ramp delay */
+       popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+       popn &= TWL4030_RAMP_DELAY;
+       popn |= TWL4030_RAMP_DELAY_645MS;
+       twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
+       popn |= TWL4030_VMID_EN;
+       twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
+
+       /* enable output stage and gain setting */
+       hsgain = TWL4030_HSR_GAIN_0DB | TWL4030_HSL_GAIN_0DB;
+       twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hsgain);
+
+       /* enable anti-pop ramp */
+       popn |= TWL4030_RAMP_EN;
+       twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
+}
+
+static void twl4030_power_down(struct snd_soc_codec *codec)
+{
+       u8 popn, hsgain;
+
+       /* disable anti-pop ramp */
+       popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+       popn &= ~TWL4030_RAMP_EN;
+       twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
+
+       /* disable output stage and gain setting */
+       hsgain = TWL4030_HSR_GAIN_PWR_DOWN | TWL4030_HSL_GAIN_PWR_DOWN;
+       twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hsgain);
+
+       /* disable bias out */
+       popn &= ~TWL4030_VMID_EN;
+       twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
+
+       /* power down */
+       twl4030_clear_codecpdz(codec);
+}
+
+static int twl4030_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               twl4030_power_up(codec);
+               break;
+       case SND_SOC_BIAS_PREPARE:
+               /* TODO: develop a twl4030_prepare function */
+               break;
+       case SND_SOC_BIAS_STANDBY:
+               /* TODO: develop a twl4030_standby function */
+               twl4030_power_down(codec);
+               break;
+       case SND_SOC_BIAS_OFF:
+               twl4030_power_down(codec);
+               break;
+       }
+       codec->bias_level = level;
+
+       return 0;
+}
+
+static int twl4030_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_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->codec;
+       u8 mode, old_mode, format, old_format;
+
+
+       /* bit rate */
+       old_mode = twl4030_read_reg_cache(codec,
+                       TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
+       mode = old_mode & ~TWL4030_APLL_RATE;
+
+       switch (params_rate(params)) {
+       case 8000:
+               mode |= TWL4030_APLL_RATE_8000;
+               break;
+       case 11025:
+               mode |= TWL4030_APLL_RATE_11025;
+               break;
+       case 12000:
+               mode |= TWL4030_APLL_RATE_12000;
+               break;
+       case 16000:
+               mode |= TWL4030_APLL_RATE_16000;
+               break;
+       case 22050:
+               mode |= TWL4030_APLL_RATE_22050;
+               break;
+       case 24000:
+               mode |= TWL4030_APLL_RATE_24000;
+               break;
+       case 32000:
+               mode |= TWL4030_APLL_RATE_32000;
+               break;
+       case 44100:
+               mode |= TWL4030_APLL_RATE_44100;
+               break;
+       case 48000:
+               mode |= TWL4030_APLL_RATE_48000;
+               break;
+       default:
+               printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n",
+                       params_rate(params));
+               return -EINVAL;
+       }
+
+       if (mode != old_mode) {
+               /* change rate and set CODECPDZ */
+               twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+               twl4030_set_codecpdz(codec);
+       }
+
+       /* sample size */
+       old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+       format = old_format;
+       format &= ~TWL4030_DATA_WIDTH;
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               format |= TWL4030_DATA_WIDTH_16S_16W;
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               format |= TWL4030_DATA_WIDTH_32S_24W;
+               break;
+       default:
+               printk(KERN_ERR "TWL4030 hw params: unknown format %d\n",
+                       params_format(params));
+               return -EINVAL;
+       }
+
+       if (format != old_format) {
+
+               /* clear CODECPDZ before changing format (codec requirement) */
+               twl4030_clear_codecpdz(codec);
+
+               /* change format */
+               twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+
+               /* set CODECPDZ afterwards */
+               twl4030_set_codecpdz(codec);
+       }
+       return 0;
+}
+
+static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+               int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u8 infreq;
+
+       switch (freq) {
+       case 19200000:
+               infreq = TWL4030_APLL_INFREQ_19200KHZ;
+               break;
+       case 26000000:
+               infreq = TWL4030_APLL_INFREQ_26000KHZ;
+               break;
+       case 38400000:
+               infreq = TWL4030_APLL_INFREQ_38400KHZ;
+               break;
+       default:
+               printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n",
+                       freq);
+               return -EINVAL;
+       }
+
+       infreq |= TWL4030_APLL_EN;
+       twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq);
+
+       return 0;
+}
+
+static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
+                            unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u8 old_format, format;
+
+       /* get format */
+       old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+       format = old_format;
+
+       /* set master/slave audio interface */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               format &= ~(TWL4030_AIF_SLAVE_EN);
+               format |= TWL4030_CLK256FS_EN;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               format &= ~(TWL4030_CLK256FS_EN);
+               format |= TWL4030_AIF_SLAVE_EN;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* interface format */
+       format &= ~TWL4030_AIF_FORMAT;
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               format |= TWL4030_AIF_FORMAT_CODEC;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (format != old_format) {
+
+               /* clear CODECPDZ before changing format (codec requirement) */
+               twl4030_clear_codecpdz(codec);
+
+               /* change format */
+               twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+
+               /* set CODECPDZ afterwards */
+               twl4030_set_codecpdz(codec);
+       }
+
+       return 0;
+}
+
+#define TWL4030_RATES   (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define TWL4030_FORMATS         (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
+
+struct snd_soc_dai twl4030_dai = {
+       .name = "twl4030",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = TWL4030_RATES,
+               .formats = TWL4030_FORMATS,},
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = TWL4030_RATES,
+               .formats = TWL4030_FORMATS,},
+       .ops = {
+               .hw_params = twl4030_hw_params,
+       },
+       .dai_ops = {
+               .set_sysclk = twl4030_set_dai_sysclk,
+               .set_fmt = twl4030_set_dai_fmt,
+       }
+};
+EXPORT_SYMBOL_GPL(twl4030_dai);
+
+static int twl4030_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->codec;
+
+       twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int twl4030_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->codec;
+
+       twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+       twl4030_set_bias_level(codec, codec->suspend_bias_level);
+       return 0;
+}
+
+/*
+ * initialize the driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+
+static int twl4030_init(struct snd_soc_device *socdev)
+{
+       struct snd_soc_codec *codec = socdev->codec;
+       int ret = 0;
+
+       printk(KERN_INFO "TWL4030 Audio Codec init \n");
+
+       codec->name = "twl4030";
+       codec->owner = THIS_MODULE;
+       codec->read = twl4030_read_reg_cache;
+       codec->write = twl4030_write;
+       codec->set_bias_level = twl4030_set_bias_level;
+       codec->dai = &twl4030_dai;
+       codec->num_dai = 1;
+       codec->reg_cache_size = sizeof(twl4030_reg);
+       codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg),
+                                       GFP_KERNEL);
+       if (codec->reg_cache == NULL)
+               return -ENOMEM;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               printk(KERN_ERR "twl4030: failed to create pcms\n");
+               goto pcm_err;
+       }
+
+       twl4030_init_chip(codec);
+
+       /* power on device */
+       twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       twl4030_add_controls(codec);
+       twl4030_add_widgets(codec);
+
+       ret = snd_soc_register_card(socdev);
+       if (ret < 0) {
+               printk(KERN_ERR "twl4030: failed to register card\n");
+               goto card_err;
+       }
+
+       return ret;
+
+card_err:
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+pcm_err:
+       kfree(codec->reg_cache);
+       return ret;
+}
+
+static struct snd_soc_device *twl4030_socdev;
+
+static int twl4030_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+
+       codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+       if (codec == NULL)
+               return -ENOMEM;
+
+       socdev->codec = codec;
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       twl4030_socdev = socdev;
+       twl4030_init(socdev);
+
+       return 0;
+}
+
+static int twl4030_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->codec;
+
+       printk(KERN_INFO "TWL4030 Audio Codec remove\n");
+       kfree(codec);
+
+       return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_twl4030 = {
+       .probe = twl4030_probe,
+       .remove = twl4030_remove,
+       .suspend = twl4030_suspend,
+       .resume = twl4030_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030);
+
+MODULE_DESCRIPTION("ASoC TWL4030 codec driver");
+MODULE_AUTHOR("Steve Sakoman");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h
new file mode 100644 (file)
index 0000000..09865d9
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * ALSA SoC TWL4030 codec driver
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TWL4030_AUDIO_H__
+#define __TWL4030_AUDIO_H__
+
+#define TWL4030_REG_CODEC_MODE         0x1
+#define TWL4030_REG_OPTION             0x2
+#define TWL4030_REG_UNKNOWN            0x3
+#define TWL4030_REG_MICBIAS_CTL                0x4
+#define TWL4030_REG_ANAMICL            0x5
+#define TWL4030_REG_ANAMICR            0x6
+#define TWL4030_REG_AVADC_CTL          0x7
+#define TWL4030_REG_ADCMICSEL          0x8
+#define TWL4030_REG_DIGMIXING          0x9
+#define TWL4030_REG_ATXL1PGA           0xA
+#define TWL4030_REG_ATXR1PGA           0xB
+#define TWL4030_REG_AVTXL2PGA          0xC
+#define TWL4030_REG_AVTXR2PGA          0xD
+#define TWL4030_REG_AUDIO_IF           0xE
+#define TWL4030_REG_VOICE_IF           0xF
+#define TWL4030_REG_ARXR1PGA           0x10
+#define TWL4030_REG_ARXL1PGA           0x11
+#define TWL4030_REG_ARXR2PGA           0x12
+#define TWL4030_REG_ARXL2PGA           0x13
+#define TWL4030_REG_VRXPGA             0x14
+#define TWL4030_REG_VSTPGA             0x15
+#define TWL4030_REG_VRX2ARXPGA         0x16
+#define TWL4030_REG_AVDAC_CTL          0x17
+#define TWL4030_REG_ARX2VTXPGA         0x18
+#define TWL4030_REG_ARXL1_APGA_CTL     0x19
+#define TWL4030_REG_ARXR1_APGA_CTL     0x1A
+#define TWL4030_REG_ARXL2_APGA_CTL     0x1B
+#define TWL4030_REG_ARXR2_APGA_CTL     0x1C
+#define TWL4030_REG_ATX2ARXPGA         0x1D
+#define TWL4030_REG_BT_IF              0x1E
+#define TWL4030_REG_BTPGA              0x1F
+#define TWL4030_REG_BTSTPGA            0x20
+#define TWL4030_REG_EAR_CTL            0x21
+#define TWL4030_REG_HS_SEL             0x22
+#define TWL4030_REG_HS_GAIN_SET                0x23
+#define TWL4030_REG_HS_POPN_SET                0x24
+#define TWL4030_REG_PREDL_CTL          0x25
+#define TWL4030_REG_PREDR_CTL          0x26
+#define TWL4030_REG_PRECKL_CTL         0x27
+#define TWL4030_REG_PRECKR_CTL         0x28
+#define TWL4030_REG_HFL_CTL            0x29
+#define TWL4030_REG_HFR_CTL            0x2A
+#define TWL4030_REG_ALC_CTL            0x2B
+#define TWL4030_REG_ALC_SET1           0x2C
+#define TWL4030_REG_ALC_SET2           0x2D
+#define TWL4030_REG_BOOST_CTL          0x2E
+#define TWL4030_REG_SOFTVOL_CTL                0x2F
+#define TWL4030_REG_DTMF_FREQSEL       0x30
+#define TWL4030_REG_DTMF_TONEXT1H      0x31
+#define TWL4030_REG_DTMF_TONEXT1L      0x32
+#define TWL4030_REG_DTMF_TONEXT2H      0x33
+#define TWL4030_REG_DTMF_TONEXT2L      0x34
+#define TWL4030_REG_DTMF_TONOFF                0x35
+#define TWL4030_REG_DTMF_WANONOFF      0x36
+#define TWL4030_REG_I2S_RX_SCRAMBLE_H  0x37
+#define TWL4030_REG_I2S_RX_SCRAMBLE_M  0x38
+#define TWL4030_REG_I2S_RX_SCRAMBLE_L  0x39
+#define TWL4030_REG_APLL_CTL           0x3A
+#define TWL4030_REG_DTMF_CTL           0x3B
+#define TWL4030_REG_DTMF_PGA_CTL2      0x3C
+#define TWL4030_REG_DTMF_PGA_CTL1      0x3D
+#define TWL4030_REG_MISC_SET_1         0x3E
+#define TWL4030_REG_PCMBTMUX           0x3F
+#define TWL4030_REG_RX_PATH_SEL                0x43
+#define TWL4030_REG_VDL_APGA_CTL       0x44
+#define TWL4030_REG_VIBRA_CTL          0x45
+#define TWL4030_REG_VIBRA_SET          0x46
+#define TWL4030_REG_VIBRA_PWM_SET      0x47
+#define TWL4030_REG_ANAMIC_GAIN                0x48
+#define TWL4030_REG_MISC_SET_2         0x49
+
+#define TWL4030_CACHEREGNUM    (TWL4030_REG_MISC_SET_2 + 1)
+
+/* Bitfield Definitions */
+
+/* TWL4030_CODEC_MODE (0x01) Fields */
+
+#define TWL4030_APLL_RATE              0xF0
+#define TWL4030_APLL_RATE_8000         0x00
+#define TWL4030_APLL_RATE_11025                0x10
+#define TWL4030_APLL_RATE_12000                0x20
+#define TWL4030_APLL_RATE_16000                0x40
+#define TWL4030_APLL_RATE_22050                0x50
+#define TWL4030_APLL_RATE_24000                0x60
+#define TWL4030_APLL_RATE_32000                0x80
+#define TWL4030_APLL_RATE_44100                0x90
+#define TWL4030_APLL_RATE_48000                0xA0
+#define TWL4030_SEL_16K                        0x04
+#define TWL4030_CODECPDZ               0x02
+#define TWL4030_OPT_MODE               0x01
+
+/* ANAMICL (0x05) Fields */
+#define TWL4030_CNCL_OFFSET_START      0x80
+#define TWL4030_OFFSET_CNCL_SEL                0x60
+#define TWL4030_OFFSET_CNCL_SEL_ARX1   0x00
+#define TWL4030_OFFSET_CNCL_SEL_ARX2   0x20
+#define TWL4030_OFFSET_CNCL_SEL_VRX    0x40
+#define TWL4030_OFFSET_CNCL_SEL_ALL    0x60
+#define TWL4030_MICAMPL_EN             0x10
+#define TWL4030_CKMIC_EN               0x08
+#define TWL4030_AUXL_EN                        0x04
+#define TWL4030_HSMIC_EN               0x02
+#define TWL4030_MAINMIC_EN             0x01
+
+/* ANAMICR (0x06) Fields */
+#define TWL4030_MICAMPR_EN             0x10
+#define TWL4030_AUXR_EN                        0x04
+#define TWL4030_SUBMIC_EN              0x01
+
+/* AUDIO_IF (0x0E) Fields */
+
+#define TWL4030_AIF_SLAVE_EN           0x80
+#define TWL4030_DATA_WIDTH             0x60
+#define TWL4030_DATA_WIDTH_16S_16W     0x00
+#define TWL4030_DATA_WIDTH_32S_16W     0x40
+#define TWL4030_DATA_WIDTH_32S_24W     0x60
+#define TWL4030_AIF_FORMAT             0x18
+#define TWL4030_AIF_FORMAT_CODEC       0x00
+#define TWL4030_AIF_FORMAT_LEFT                0x08
+#define TWL4030_AIF_FORMAT_RIGHT       0x10
+#define TWL4030_AIF_FORMAT_TDM         0x18
+#define TWL4030_AIF_TRI_EN             0x04
+#define TWL4030_CLK256FS_EN            0x02
+#define TWL4030_AIF_EN                 0x01
+
+/* HS_GAIN_SET (0x23) Fields */
+
+#define TWL4030_HSR_GAIN               0x0C
+#define TWL4030_HSR_GAIN_PWR_DOWN      0x00
+#define TWL4030_HSR_GAIN_PLUS_6DB      0x04
+#define TWL4030_HSR_GAIN_0DB           0x08
+#define TWL4030_HSR_GAIN_MINUS_6DB     0x0C
+#define TWL4030_HSL_GAIN               0x03
+#define TWL4030_HSL_GAIN_PWR_DOWN      0x00
+#define TWL4030_HSL_GAIN_PLUS_6DB      0x01
+#define TWL4030_HSL_GAIN_0DB           0x02
+#define TWL4030_HSL_GAIN_MINUS_6DB     0x03
+
+/* HS_POPN_SET (0x24) Fields */
+
+#define TWL4030_VMID_EN                        0x40
+#define        TWL4030_EXTMUTE                 0x20
+#define TWL4030_RAMP_DELAY             0x1C
+#define TWL4030_RAMP_DELAY_20MS                0x00
+#define TWL4030_RAMP_DELAY_40MS                0x04
+#define TWL4030_RAMP_DELAY_81MS                0x08
+#define TWL4030_RAMP_DELAY_161MS       0x0C
+#define TWL4030_RAMP_DELAY_323MS       0x10
+#define TWL4030_RAMP_DELAY_645MS       0x14
+#define TWL4030_RAMP_DELAY_1291MS      0x18
+#define TWL4030_RAMP_DELAY_2581MS      0x1C
+#define TWL4030_RAMP_EN                        0x02
+
+/* APLL_CTL (0x3A) Fields */
+
+#define TWL4030_APLL_EN                        0x10
+#define TWL4030_APLL_INFREQ            0x0F
+#define TWL4030_APLL_INFREQ_19200KHZ   0x05
+#define TWL4030_APLL_INFREQ_26000KHZ   0x06
+#define TWL4030_APLL_INFREQ_38400KHZ   0x0F
+
+/* REG_MISC_SET_1 (0x3E) Fields */
+
+#define TWL4030_CLK64_EN               0x80
+#define TWL4030_SCRAMBLE_EN            0x40
+#define TWL4030_FMLOOP_EN              0x20
+#define TWL4030_SMOOTH_ANAVOL_EN       0x02
+#define TWL4030_DIGMIC_LR_SWAP_EN      0x01
+
+extern struct snd_soc_dai twl4030_dai;
+extern struct snd_soc_codec_device soc_codec_dev_twl4030;
+
+#endif /* End of __TWL4030_AUDIO_H__ */