summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorRob Herring <r.herring@freescale.com>2009-05-05 14:25:13 -0500
committerRob Herring <r.herring@freescale.com>2009-05-05 18:17:51 -0500
commitc096fc73812248072b8c62a071dfe565fa359983 (patch)
tree1b67d3591a8e9d009ca5fcf37d8de654f9cdf33f /sound
parent4e9da5716840fa8458211b98191ccdebcec74a67 (diff)
ENGR00112199 Import EA 3780 release 4
This is from EA P4 release with the following changes: Ported to 2.6.28 UBI support is stock 2.6.28. USB is not integrated Regulator code is not yet ported. Removed 3700 specific files Fix copyrights Signed-off-by: Rob Herring <r.herring@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig3
-rw-r--r--sound/soc/Makefile2
-rw-r--r--sound/soc/codecs/Kconfig8
-rw-r--r--sound/soc/codecs/Makefile4
-rw-r--r--sound/soc/codecs/stmp378x_codec.c775
-rw-r--r--sound/soc/codecs/stmp378x_codec.h87
-rw-r--r--sound/soc/codecs/stmp3xxx_spdif.c412
-rw-r--r--sound/soc/codecs/stmp3xxx_spdif.h37
-rw-r--r--sound/soc/stmp3xxx/Kconfig31
-rw-r--r--sound/soc/stmp3xxx/Makefile15
-rw-r--r--sound/soc/stmp3xxx/stmp3780_devb.c97
-rw-r--r--sound/soc/stmp3xxx/stmp3780_devb_spdif.c95
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_dai.c224
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_dai.h21
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_pcm.c440
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_pcm.h30
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c184
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h21
18 files changed, 2484 insertions, 2 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 2d8f05baf721..1e48ea18e5f8 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -10,7 +10,7 @@ menuconfig SND_SOC
If you want ASoC support, you should say Y here and also to the
specific driver for your SoC platform below.
-
+
ASoC provides power efficient ALSA support for embedded battery powered
SoC based systems like PDA's, Phones and Personal Media Players.
@@ -30,6 +30,7 @@ source "sound/soc/pxa/Kconfig"
source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/imx/Kconfig"
+source "sound/soc/stmp3xxx/Kconfig"
source "sound/soc/fsl/Kconfig"
source "sound/soc/davinci/Kconfig"
source "sound/soc/omap/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 850776caea15..d997c29d2d8a 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -1,5 +1,5 @@
snd-soc-core-objs := soc-core.o soc-dapm.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
-obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ imx/
+obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ imx/ stmp3xxx/
obj-$(CONFIG_SND_SOC) += omap/ au1x/ blackfin/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index cf0fcb03ef69..45a12880d3bb 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -122,3 +122,11 @@ config SND_SOC_SGTL5000
config SND_SOC_AK4647
tristate
depends on I2C
+
+config SND_SOC_STMP378X_CODEC
+ tristate
+ depends on SND_SOC
+
+config SND_SOC_STMP3XXX_SPDIF
+ tristate
+ depends on SND_SOC
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index e47aa87358ce..b5e5b51bc4e1 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -22,6 +22,8 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-ak4647-objs := ak4647.o
+snd-soc-stmp378x-codec-objs := stmp378x_codec.o
+snd-soc-stmp3xxx-spdif-objs := stmp3xxx_spdif.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
@@ -47,3 +49,5 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_AK4647) += snd-soc-ak4647.o
+obj-$(CONFIG_SND_SOC_STMP378X_CODEC) += snd-soc-stmp378x-codec.o
+obj-$(CONFIG_SND_SOC_STMP3XXX_SPDIF) += snd-soc-stmp3xxx-spdif.o
diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c
new file mode 100644
index 000000000000..0893894efef5
--- /dev/null
+++ b/sound/soc/codecs/stmp378x_codec.c
@@ -0,0 +1,775 @@
+/*
+ * ALSA codec for Freescale STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.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 <asm/dma.h>
+
+#include <mach/regs-apbx.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+#include <mach/regs-rtc.h>
+
+#include "stmp378x_codec.h"
+
+#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */
+
+#define STMP378X_VERSION "0.1"
+struct stmp378x_codec_priv {
+ struct device *dev;
+ struct clk *clk;
+};
+
+/*
+ * ALSA API
+ */
+static u32 adc_regmap[] = {
+ HW_AUDIOOUT_CTRL_ADDR,
+ HW_AUDIOOUT_STAT_ADDR,
+ HW_AUDIOOUT_DACSRR_ADDR,
+ HW_AUDIOOUT_DACVOLUME_ADDR,
+ HW_AUDIOOUT_DACDEBUG_ADDR,
+ HW_AUDIOOUT_HPVOL_ADDR,
+ HW_AUDIOOUT_PWRDN_ADDR,
+ HW_AUDIOOUT_REFCTRL_ADDR,
+ HW_AUDIOOUT_ANACTRL_ADDR,
+ HW_AUDIOOUT_TEST_ADDR,
+ HW_AUDIOOUT_BISTCTRL_ADDR,
+ HW_AUDIOOUT_BISTSTAT0_ADDR,
+ HW_AUDIOOUT_BISTSTAT1_ADDR,
+ HW_AUDIOOUT_ANACLKCTRL_ADDR,
+ HW_AUDIOOUT_DATA_ADDR,
+ HW_AUDIOOUT_SPEAKERCTRL_ADDR,
+ HW_AUDIOOUT_VERSION_ADDR,
+ HW_AUDIOIN_CTRL_ADDR,
+ HW_AUDIOIN_STAT_ADDR,
+ HW_AUDIOIN_ADCSRR_ADDR,
+ HW_AUDIOIN_ADCVOLUME_ADDR,
+ HW_AUDIOIN_ADCDEBUG_ADDR,
+ HW_AUDIOIN_ADCVOL_ADDR,
+ HW_AUDIOIN_MICLINE_ADDR,
+ HW_AUDIOIN_ANACLKCTRL_ADDR,
+ HW_AUDIOIN_DATA_ADDR,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit ADC/DAC registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static int stmp378x_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= ADC_REGNUM)
+ return -EIO;
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, adc_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int stmp378x_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= ADC_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+static const char *stmp378x_codec_adc_input_sel[] =
+ {"Mic", "Line In 1", "Head Phone", "Line In 2"};
+
+static const char *stmp378x_codec_hp_output_sel[] =
+ {"DAC", "Line In 1"};
+
+static const char *stmp378x_codec_adc_3d_sel[] =
+ {"Off", "Low", "Medium", "High"};
+
+static const struct soc_enum stmp378x_codec_enum[] = {
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, stmp378x_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, stmp378x_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, stmp378x_codec_hp_output_sel),
+ SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, stmp378x_codec_adc_3d_sel),
+};
+
+/* Codec controls */
+static const struct snd_kcontrol_new stmp378x_snd_controls[] = {
+ /* Playback Volume */
+ SOC_DOUBLE_R("DAC Playback Volume",
+ DAC_VOLUME_H, DAC_VOLUME_L, 0, 0xFF, 0),
+ SOC_DOUBLE_R("DAC Playback Switch",
+ DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1),
+ SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1),
+ SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1),
+ SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1),
+
+ /* Capture Volume */
+ SOC_DOUBLE_R("ADC Capture Volume",
+ ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0),
+ SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0),
+ SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1),
+ SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0),
+
+ /* Virtual 3D effect */
+ SOC_ENUM("3D effect", stmp378x_codec_enum[3]),
+};
+
+/* add non dapm controls */
+static int stmp378x_codec_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(stmp378x_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&stmp378x_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Left ADC Mux */
+static const struct snd_kcontrol_new stmp378x_left_adc_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[0]);
+
+/* Right ADC Mux */
+static const struct snd_kcontrol_new stmp378x_right_adc_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[1]);
+
+/* Head Phone Mux */
+static const struct snd_kcontrol_new stmp378x_hp_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[2]);
+
+static const struct snd_soc_dapm_widget stmp378x_codec_widgets[] = {
+
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_left_adc_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_right_adc_controls),
+ SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_hp_controls),
+
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+ SND_SOC_DAPM_INPUT("MIC"),
+
+ SND_SOC_DAPM_OUTPUT("SPEAKER"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+};
+static const struct snd_soc_dapm_route intercon[] = {
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Mic", "MIC"},
+ {"Left ADC Mux", "Line In 1", "LINE1L"},
+ {"Left ADC Mux", "Line In 2", "LINE2L"},
+ {"Left ADC Mux", "Head Phone", "HPL"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Mic", "MIC"},
+ {"Right ADC Mux", "Line In 1", "LINE1R"},
+ {"Right ADC Mux", "Line In 2", "LINE2R"},
+ {"Right ADC Mux", "Head Phone", "HPR"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+
+ /* HP Mux */
+ {"HP Mux", "DAC", "Left DAC"},
+ {"HP Mux", "DAC", "Right DAC"},
+ {"HP Mux", "Line In 1", "LINE1L"},
+ {"HP Mux", "Line In 1", "LINE1R"},
+
+ /* HP output */
+ {"HPR", NULL, "HP Mux"},
+ {"HPL", NULL, "HP Mux"},
+
+ /* Speaker amp */
+ {"SPEAKER", NULL, "Right DAC"},
+};
+
+static int stmp378x_codec_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, stmp378x_codec_widgets,
+ ARRAY_SIZE(stmp378x_codec_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct dac_srr {
+ u32 rate;
+ u32 basemult;
+ u32 src_hold;
+ u32 src_int;
+ u32 src_frac;
+};
+
+static struct dac_srr srr_values[] = {
+ {192000, 0x4, 0x0, 0x0F, 0x13FF},
+ {176400, 0x4, 0x0, 0x11, 0x0037},
+ {128000, 0x4, 0x0, 0x17, 0x0E00},
+ {96000, 0x2, 0x0, 0x0F, 0x13FF},
+ {88200, 0x2, 0x0, 0x11, 0x0037},
+ {64000, 0x2, 0x0, 0x17, 0x0E00},
+ {48000, 0x1, 0x0, 0x0F, 0x13FF},
+ {44100, 0x1, 0x0, 0x11, 0x0037},
+ {32000, 0x1, 0x0, 0x17, 0x0E00},
+ {24000, 0x1, 0x1, 0x0F, 0x13FF},
+ {22050, 0x1, 0x1, 0x11, 0x0037},
+ {16000, 0x1, 0x1, 0x17, 0x0E00},
+ {12000, 0x1, 0x3, 0x0F, 0x13FF},
+ {11025, 0x1, 0x3, 0x11, 0x0037},
+ {8000, 0x1, 0x3, 0x17, 0x0E00}
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int stmp378x_codec_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;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 src_hold = 0;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ printk(KERN_WARNING "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ src_hold = srr_values[i].src_hold;
+
+ srr_value =
+ BF_AUDIOOUT_DACSRR_BASEMULT(srr_values[i].basemult) |
+ BF_AUDIOOUT_DACSRR_SRC_INT(srr_values[i].src_int) |
+ BF_AUDIOOUT_DACSRR_SRC_FRAC(srr_values[i].src_frac) |
+ BF_AUDIOOUT_DACSRR_SRC_HOLD(src_hold);
+
+ if (playback)
+ HW_AUDIOOUT_DACSRR_WR(srr_value);
+ else
+ HW_AUDIOIN_ADCSRR_WR(srr_value);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_WORD_LENGTH);
+ else
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_WORD_LENGTH);
+
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_WORD_LENGTH);
+ else
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_WORD_LENGTH);
+
+ break;
+
+ default:
+ printk(KERN_WARNING "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+
+ }
+
+ return 0;
+}
+
+static int stmp378x_codec_dig_mute(struct snd_soc_dai *dai, int mute)
+{
+ u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT;
+
+ if (mute)
+ HW_AUDIOOUT_DACVOLUME_SET(dac_mask);
+ else
+ HW_AUDIOOUT_DACVOLUME_CLR(dac_mask);
+
+ return 0;
+}
+
+/*
+ * Codec initialization
+ */
+#define VAG_BASE_VALUE ((1400/2 - 625)/25)
+static void stmp378x_codec_dac_set_vag(void)
+{
+ u32 refctrl_val = HW_AUDIOOUT_REFCTRL_RD();
+
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL);
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ);
+ refctrl_val |= BF_AUDIOOUT_REFCTRL_VAG_VAL(VAG_BASE_VALUE) |
+ BM_AUDIOOUT_REFCTRL_ADJ_VAG |
+ BF_AUDIOOUT_REFCTRL_ADC_REFVAL(0xF) |
+ BM_AUDIOOUT_REFCTRL_ADJ_ADC |
+ BF_AUDIOOUT_REFCTRL_VBG_ADJ(0x3) |
+ BM_AUDIOOUT_REFCTRL_RAISE_REF;
+
+ HW_AUDIOOUT_REFCTRL_WR(refctrl_val);
+}
+
+static void
+stmp378x_codec_dac_power_on(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Ungate DAC clocks */
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_CLKGATE);
+ HW_AUDIOOUT_ANACLKCTRL_CLR(BM_AUDIOOUT_ANACLKCTRL_CLKGATE);
+
+ /* Set capless mode */
+ HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_CAPLESS);
+
+ /* 16 bit word length */
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_WORD_LENGTH);
+
+ /* Power up DAC */
+ HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_DAC);
+ /* Update DAC volume over zero crossings */
+ HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_EN_ZCD);
+ /* Mute DAC */
+ HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT);
+
+ /* Update HP volume over zero crossings */
+ HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD);
+
+ /* Power up HP output */
+ HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND);
+ HW_RTC_PERSISTENT0_SET(BF_RTC_PERSISTENT0_SPARE_ANALOG(0x2));
+ HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_HEADPHONE);
+ HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_CLASSAB);
+ HW_AUDIOOUT_ANACTRL_CLR(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND);
+ /* Mute HP output */
+ HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_MUTE);
+
+ /* Power up speaker amp */
+ HW_AUDIOOUT_PWRDN_CLR(BM_AUDIOOUT_PWRDN_SPEAKER);
+ /* Mute speaker amp */
+ HW_AUDIOOUT_SPEAKERCTRL_SET(BM_AUDIOOUT_SPEAKERCTRL_MUTE);
+}
+
+static void
+stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Disable class AB */
+ HW_AUDIOOUT_ANACTRL_CLR(BM_AUDIOOUT_ANACTRL_HP_CLASSAB);
+
+ /* Set hold to ground */
+ HW_AUDIOOUT_ANACTRL_SET(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND);
+
+ /* Mute HP output */
+ HW_AUDIOOUT_HPVOL_SET(BM_AUDIOOUT_HPVOL_MUTE);
+ /* Power down HP output */
+ HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_HEADPHONE);
+
+ /* Mute speaker amp */
+ HW_AUDIOOUT_SPEAKERCTRL_SET(BM_AUDIOOUT_SPEAKERCTRL_MUTE);
+ /* Power down speaker amp */
+ HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_SPEAKER);
+
+ /* Mute DAC */
+ HW_AUDIOOUT_DACVOLUME_SET(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT);
+ /* Power down DAC */
+ HW_AUDIOOUT_PWRDN_SET(BM_AUDIOOUT_PWRDN_DAC);
+
+ /* Gate DAC clocks */
+ HW_AUDIOOUT_ANACLKCTRL_SET(BM_AUDIOOUT_ANACLKCTRL_CLKGATE);
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_CLKGATE);
+}
+
+static void
+stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ u32 reg;
+
+ /* Ungate ADC clocks */
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_CLKGATE);
+ HW_AUDIOIN_ANACLKCTRL_CLR(BM_AUDIOIN_ANACLKCTRL_CLKGATE);
+
+ /* Power Up ADC */
+ HW_AUDIOOUT_PWRDN_CLR(
+ BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC);
+
+ /* 16 bit word length */
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_WORD_LENGTH);
+
+ /* Unmute ADC channels */
+ HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_MUTE);
+
+ /*
+ * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared.
+ * They aren't presented in the datasheet, so this is hardcode.
+ */
+ HW_AUDIOIN_ADCVOLUME_CLR(0x01000100);
+
+ /* Set the Input channel gain 3dB */
+ HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_GAIN_LEFT);
+ HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_GAIN_RIGHT);
+ HW_AUDIOIN_ADCVOL_SET(BF_AUDIOIN_ADCVOL_GAIN_LEFT(2));
+ HW_AUDIOIN_ADCVOL_SET(BF_AUDIOIN_ADCVOL_GAIN_RIGHT(2));
+
+ /* Select default input - Microphone */
+ HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_SELECT_LEFT);
+ HW_AUDIOIN_ADCVOL_CLR(BM_AUDIOIN_ADCVOL_SELECT_RIGHT);
+ HW_AUDIOIN_ADCVOL_SET(
+ BF_AUDIOIN_ADCVOL_SELECT_LEFT(BV_AUDIOIN_ADCVOL_SELECT__MIC));
+ HW_AUDIOIN_ADCVOL_SET(
+ BF_AUDIOIN_ADCVOL_SELECT_RIGHT(BV_AUDIOIN_ADCVOL_SELECT__MIC));
+
+ /* Set max ADC volume */
+ reg = HW_AUDIOIN_ADCVOLUME_RD();
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+ reg |= BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MAX);
+ reg |= BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MAX);
+ HW_AUDIOIN_ADCVOLUME_WR(reg);
+}
+
+static void
+stmp378x_codec_adc_power_down(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Mute ADC channels */
+ HW_AUDIOIN_ADCVOL_SET(BM_AUDIOIN_ADCVOL_MUTE);
+
+ /* Power Down ADC */
+ HW_AUDIOOUT_PWRDN_SET(
+ BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC);
+
+ /* Gate ADC clocks */
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_CLKGATE);
+ HW_AUDIOIN_ANACLKCTRL_SET(BM_AUDIOIN_ANACLKCTRL_CLKGATE);
+}
+
+static void
+stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Move DAC codec out of reset */
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_SFTRST);
+
+ /* Reduce analog power */
+ HW_AUDIOOUT_TEST_CLR(BM_AUDIOOUT_TEST_HP_I1_ADJ);
+ HW_AUDIOOUT_TEST_SET(BF_AUDIOOUT_TEST_HP_I1_ADJ(0x1));
+ HW_AUDIOOUT_REFCTRL_SET(BM_AUDIOOUT_REFCTRL_LOW_PWR);
+ HW_AUDIOOUT_REFCTRL_SET(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS);
+ HW_AUDIOOUT_REFCTRL_CLR(BM_AUDIOOUT_REFCTRL_BIAS_CTRL);
+ HW_AUDIOOUT_REFCTRL_CLR(BF_AUDIOOUT_REFCTRL_BIAS_CTRL(0x1));
+
+ /* Set Vag value */
+ stmp378x_codec_dac_set_vag();
+
+ /* Power on DAC codec */
+ stmp378x_codec_dac_power_on(stmp378x_adc);
+}
+
+static void stmp378x_codec_dac_disable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ stmp378x_codec_dac_power_down(stmp378x_adc);
+}
+
+static void
+stmp378x_codec_adc_enable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Move ADC codec out of reset */
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_SFTRST);
+
+ /* Power on ADC codec */
+ stmp378x_codec_adc_power_on(stmp378x_adc);
+}
+
+static void stmp378x_codec_adc_disable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ stmp378x_codec_adc_power_down(stmp378x_adc);
+}
+
+static void
+stmp378x_codec_init(struct snd_soc_codec *codec)
+{
+ struct stmp378x_codec_priv *stmp378x_adc = codec->private_data;
+
+ /* Soft reset DAC block */
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_SFTRST);
+ while (!(HW_AUDIOOUT_CTRL_RD() & BM_AUDIOOUT_CTRL_CLKGATE));
+
+ /* Soft reset ADC block */
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_SFTRST);
+ while (!(HW_AUDIOIN_CTRL_RD() & BM_AUDIOIN_CTRL_CLKGATE));
+
+ stmp378x_codec_dac_enable(stmp378x_adc);
+ stmp378x_codec_adc_enable(stmp378x_adc);
+
+ stmp378x_codec_add_controls(codec);
+ stmp378x_codec_add_widgets(codec);
+}
+
+static void
+stmp378x_codec_exit(struct snd_soc_codec *codec)
+{
+ struct stmp378x_codec_priv *stmp378x_adc = codec->private_data;
+ stmp378x_codec_dac_disable(stmp378x_adc);
+ stmp378x_codec_adc_disable(stmp378x_adc);
+}
+
+#define STMP378X_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define STMP378X_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+struct snd_soc_dai stmp378x_codec_dai = {
+ .name = "stmp378x adc/dac",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP378X_ADC_RATES,
+ .formats = STMP378X_ADC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP378X_ADC_RATES,
+ .formats = STMP378X_ADC_FORMATS,
+ },
+ .ops = {
+ .hw_params = stmp378x_codec_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = stmp378x_codec_dig_mute,
+ }
+};
+EXPORT_SYMBOL_GPL(stmp378x_codec_dai);
+
+static int stmp378x_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct stmp378x_codec_priv *stmp378x_adc;
+ int ret = 0;
+
+ printk(KERN_INFO "STMP378X ADC/DAC Audio Codec %s\n", STMP378X_VERSION);
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ stmp378x_adc = kzalloc(sizeof(struct stmp378x_codec_priv), GFP_KERNEL);
+ if (stmp378x_adc == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->name = "stmp378x adc/dac";
+ codec->owner = THIS_MODULE;
+ codec->private_data = stmp378x_adc;
+ codec->read = stmp378x_codec_read;
+ codec->write = stmp378x_codec_write;
+ codec->dai = &stmp378x_codec_dai;
+ codec->num_dai = 1;
+ socdev->codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to create pcms\n", __func__);
+ goto pcm_err;
+ }
+
+ /* Turn on audio clock */
+ stmp378x_adc->dev = &pdev->dev;
+ stmp378x_adc->clk = clk_get(stmp378x_adc->dev, "audio");
+ if (IS_ERR(stmp378x_adc->clk)) {
+ ret = PTR_ERR(stmp378x_adc->clk);
+ printk(KERN_ERR "%s: Clocks initialization failed\n", __func__);
+ goto clk_err;
+ }
+ clk_enable(stmp378x_adc->clk);
+
+ stmp378x_codec_init(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to register card\n", __func__);
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ clk_disable(stmp378x_adc->clk);
+ clk_put(stmp378x_adc->clk);
+clk_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(socdev->codec);
+ return ret;
+}
+
+static int stmp378x_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct stmp378x_codec_priv *stmp378x_adc;
+
+ if (codec == NULL)
+ return 0;
+
+ stmp378x_adc = codec->private_data;
+
+ clk_disable(stmp378x_adc->clk);
+ clk_put(stmp378x_adc->clk);
+
+ stmp378x_codec_exit(codec);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ kfree(socdev->codec);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int stmp378x_codec_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;
+ struct stmp378x_codec_priv *stmp378x_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp378x_adc = codec->private_data;
+
+ stmp378x_codec_dac_disable(stmp378x_adc);
+ stmp378x_codec_adc_disable(stmp378x_adc);
+ clk_disable(stmp378x_adc->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int stmp378x_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct stmp378x_codec_priv *stmp378x_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp378x_adc = codec->private_data;
+ clk_enable(stmp378x_adc->clk);
+
+ /* Soft reset DAC block */
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_SFTRST);
+ while (!(HW_AUDIOOUT_CTRL_RD() & BM_AUDIOOUT_CTRL_CLKGATE));
+
+ /* Soft reset ADC block */
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_SFTRST);
+ while (!(HW_AUDIOIN_CTRL_RD() & BM_AUDIOIN_CTRL_CLKGATE));
+
+ stmp378x_codec_dac_enable(stmp378x_adc);
+ stmp378x_codec_adc_enable(stmp378x_adc);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define stmp378x_codec_suspend NULL
+#define stmp378x_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_codec_dev_stmp378x = {
+ .probe = stmp378x_codec_probe,
+ .remove = stmp378x_codec_remove,
+ .suspend = stmp378x_codec_suspend,
+ .resume = stmp378x_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_stmp378x);
+
+MODULE_DESCRIPTION("STMP378X ADC/DAC codec");
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stmp378x_codec.h b/sound/soc/codecs/stmp378x_codec.h
new file mode 100644
index 000000000000..80fce7273126
--- /dev/null
+++ b/sound/soc/codecs/stmp378x_codec.h
@@ -0,0 +1,87 @@
+/*
+ * ALSA codec for Freescale STMP378X
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __STMP378X_CODEC_H
+#define __STMP378X_CODEC_H
+
+#define DAC_CTRL_L 0
+#define DAC_CTRL_H 1
+#define DAC_STAT_L 2
+#define DAC_STAT_H 3
+#define DAC_SRR_L 4
+#define DAC_VOLUME_L 6
+#define DAC_VOLUME_H 7
+#define DAC_DEBUG_L 8
+#define DAC_DEBUG_H 9
+#define DAC_HPVOL_L 10
+#define DAC_HPVOL_H 11
+#define DAC_PWRDN_L 12
+#define DAC_PWRDN_H 13
+#define DAC_REFCTRL_L 14
+#define DAC_REFCTRL_H 15
+#define DAC_ANACTRL_L 16
+#define DAC_ANACTRL_H 17
+#define DAC_TEST_L 18
+#define DAC_TEST_H 19
+#define DAC_BISTCTRL_L 20
+#define DAC_BISTCTRL_H 21
+#define DAC_BISTSTAT0_L 22
+#define DAC_BISTSTAT0_H 23
+#define DAC_BISTSTAT1_L 24
+#define DAC_BISTSTAT1_H 25
+#define DAC_ANACLKCTRL_L 26
+#define DAC_ANACLKCTRL_H 27
+#define DAC_DATA_L 28
+#define DAC_DATA_H 29
+#define DAC_SPEAKERCTRL_L 30
+#define DAC_SPEAKERCTRL_H 31
+#define DAC_VERSION_L 32
+#define DAC_VERSION_H 33
+#define ADC_CTRL_L 34
+#define ADC_CTRL_H 35
+#define ADC_STAT_L 36
+#define ADC_STAT_H 37
+#define ADC_SRR_L 38
+#define ADC_SRR_H 39
+#define ADC_VOLUME_L 40
+#define ADC_VOLUME_H 41
+#define ADC_DEBUG_L 42
+#define ADC_DEBUG_H 43
+#define ADC_ADCVOL_L 44
+#define ADC_ADCVOL_H 45
+#define ADC_MICLINE_L 46
+#define ADC_MICLINE_H 47
+#define ADC_ANACLKCTRL_L 48
+#define ADC_ANACLKCTRL_H 49
+#define ADC_DATA_L 50
+#define ADC_DATA_H 51
+
+#define ADC_REGNUM 52
+
+#define DAC_VOLUME_MIN 0x37
+#define DAC_VOLUME_MAX 0xFE
+#define ADC_VOLUME_MIN 0x37
+#define ADC_VOLUME_MAX 0xFE
+#define HP_VOLUME_MAX 0x0
+#define HP_VOLUME_MIN 0x7F
+#define LO_VOLUME_MAX 0x0
+#define LO_VOLUME_MIN 0x1F
+
+extern struct snd_soc_dai stmp378x_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_stmp378x;
+
+#endif /* __STMP378X_CODEC_H */
diff --git a/sound/soc/codecs/stmp3xxx_spdif.c b/sound/soc/codecs/stmp3xxx_spdif.c
new file mode 100644
index 000000000000..9f51ec4ffbe4
--- /dev/null
+++ b/sound/soc/codecs/stmp3xxx_spdif.c
@@ -0,0 +1,412 @@
+/*
+ * ALSA SoC STMP3xxx SPDIF transmitter driver
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.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 <asm/dma.h>
+
+#include <mach/regs-spdif.h>
+
+#include "stmp3xxx_spdif.h"
+
+#define STMP3XXX_VERSION "0.1"
+struct stmp3xxx_codec_priv {
+ struct device *dev;
+ struct clk *clk;
+};
+
+/*
+ * ALSA API
+ */
+static u32 spdif_regmap[] = {
+ HW_SPDIF_CTRL_ADDR,
+ HW_SPDIF_STAT_ADDR,
+ HW_SPDIF_FRAMECTRL_ADDR,
+ HW_SPDIF_SRR_ADDR,
+ HW_SPDIF_DEBUG_ADDR,
+ HW_SPDIF_DATA_ADDR,
+ HW_SPDIF_VERSION_ADDR,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit SPDIF registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static int stmp3xxx_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= SPDIF_REGNUM)
+ return -EIO;
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, spdif_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int stmp3xxx_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= SPDIF_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+/* Codec controls */
+static const struct snd_kcontrol_new stmp3xxx_snd_controls[] = {
+ SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0),
+ SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0),
+ SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0),
+ SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0),
+ SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0),
+ SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0),
+ SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0),
+ SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0),
+ SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0),
+ SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0),
+};
+
+/* add non dapm controls */
+static int stmp3xxx_codec_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(stmp3xxx_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&stmp3xxx_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+struct spdif_srr {
+ u32 rate;
+ u32 basemult;
+ u32 rate_factor;
+};
+
+static struct spdif_srr srr_values[] = {
+ {96000, 0x2, 0x0BB80},
+ {88200, 0x2, 0x0AC44},
+ {64000, 0x2, 0x07D00},
+ {48000, 0x1, 0x0BB80},
+ {44100, 0x1, 0x0AC44},
+ {32000, 0x1, 0x07D00},
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int stmp3xxx_codec_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;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 basemult;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ printk(KERN_WARNING "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ basemult = srr_values[i].basemult;
+
+ srr_value = BF_SPDIF_SRR_BASEMULT(basemult) |
+ BF_SPDIF_SRR_RATE(srr_values[i].rate_factor);
+
+ if (playback)
+ HW_SPDIF_SRR_WR(srr_value);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_WORD_LENGTH);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_WORD_LENGTH);
+ break;
+ default:
+ printk(KERN_WARNING "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+ }
+
+ return 0;
+}
+
+static void
+stmp3xxx_codec_spdif_enable(struct stmp3xxx_codec_priv *stmp3xxx_spdif)
+{
+ /* Move SPDIF codec out of reset */
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_SFTRST);
+
+ /* Ungate SPDIF clocks */
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_CLKGATE);
+
+ /* 16 bit word length */
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_WORD_LENGTH);
+}
+
+static void
+stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif)
+{
+ /* Gate SPDIF clocks */
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_CLKGATE);
+}
+
+static void stmp3xxx_codec_init(struct snd_soc_codec *codec)
+{
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data;
+
+ /* Soft reset SPDIF block */
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_SFTRST);
+ while (!(HW_SPDIF_CTRL_RD() & BM_SPDIF_CTRL_CLKGATE));
+
+ stmp3xxx_codec_spdif_enable(stmp3xxx_spdif);
+
+ stmp3xxx_codec_add_controls(codec);
+}
+
+static void stmp3xxx_codec_exit(struct snd_soc_codec *codec)
+{
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data;
+
+ stmp3xxx_codec_spdif_disable(stmp3xxx_spdif);
+}
+
+#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai stmp3xxx_spdif_codec_dai = {
+ .name = "stmp3xxx spdif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .ops = {
+ .hw_params = stmp3xxx_codec_hw_params,
+ },
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_spdif_codec_dai);
+
+static int stmp3xxx_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = 0;
+
+ printk(KERN_INFO
+ "STMP3XXX SPDIF Audio Transmitter %s\n", STMP3XXX_VERSION);
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ stmp3xxx_spdif =
+ kzalloc(sizeof(struct stmp3xxx_codec_priv), GFP_KERNEL);
+ if (stmp3xxx_spdif == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->name = "stmp3xxx spdif";
+ codec->owner = THIS_MODULE;
+ codec->private_data = stmp3xxx_spdif;
+ codec->read = stmp3xxx_codec_read;
+ codec->write = stmp3xxx_codec_write;
+ codec->dai = &stmp3xxx_spdif_codec_dai;
+ codec->num_dai = 1;
+ socdev->codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to create pcms\n", __func__);
+ goto pcm_err;
+ }
+
+ /* Turn on audio clock */
+ stmp3xxx_spdif->dev = &pdev->dev;
+ stmp3xxx_spdif->clk = clk_get(stmp3xxx_spdif->dev, "spdif");
+ if (IS_ERR(stmp3xxx_spdif->clk)) {
+ ret = PTR_ERR(stmp3xxx_spdif->clk);
+ printk(KERN_ERR "%s: Clocks initialization failed\n", __func__);
+ goto clk_err;
+ }
+ clk_enable(stmp3xxx_spdif->clk);
+
+ stmp3xxx_codec_init(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to register card\n", __func__);
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ clk_disable(stmp3xxx_spdif->clk);
+ clk_put(stmp3xxx_spdif->clk);
+clk_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(socdev->codec);
+ return ret;
+}
+
+static int stmp3xxx_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+
+ if (codec == NULL)
+ return 0;
+
+ stmp3xxx_spdif = codec->private_data;
+
+ clk_disable(stmp3xxx_spdif->clk);
+ clk_put(stmp3xxx_spdif->clk);
+
+ stmp3xxx_codec_exit(codec);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ kfree(socdev->codec);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_codec_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;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp3xxx_spdif = codec->private_data;
+
+ stmp3xxx_codec_spdif_disable(stmp3xxx_spdif);
+ clk_disable(stmp3xxx_spdif->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int stmp3xxx_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp3xxx_spdif = codec->private_data;
+ clk_enable(stmp3xxx_spdif->clk);
+
+ /* Soft reset SPDIF block */
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_SFTRST);
+ while (!(HW_SPDIF_CTRL_RD() & BM_SPDIF_CTRL_CLKGATE));
+
+ stmp3xxx_codec_spdif_enable(stmp3xxx_spdif);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define stmp3xxx_codec_suspend NULL
+#define stmp3xxx_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx = {
+ .probe = stmp3xxx_codec_probe,
+ .remove = stmp3xxx_codec_remove,
+ .suspend = stmp3xxx_codec_suspend,
+ .resume = stmp3xxx_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_stmp3xxx);
+
+MODULE_DESCRIPTION("STMP3XXX SPDIF transmitter");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stmp3xxx_spdif.h b/sound/soc/codecs/stmp3xxx_spdif.h
new file mode 100644
index 000000000000..0ae20a7e8cc6
--- /dev/null
+++ b/sound/soc/codecs/stmp3xxx_spdif.h
@@ -0,0 +1,37 @@
+/*
+ * ALSA SoC STMP378x SPDIF codec driver
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#ifndef __STMP3XXX_SPDIF_CODEC_H
+#define __STMP3XXX_SPDIF_CODEC_H
+
+#define SPDIF_CTRL_L 0
+#define SPDIF_CTRL_H 1
+#define SPDIF_STAT_L 2
+#define SPDIF_STAT_H 3
+#define SPDIF_FRAMECTRL_L 4
+#define SPDIF_FRAMECTRL_H 5
+#define SPDIF_SRR_L 6
+#define SPDIF_SRR_H 7
+#define SPDIF_DEBUG_L 8
+#define SPDIF_DEBUG_H 9
+#define SPDIF_DATA_L 10
+#define SPDIF_DATA_H 11
+#define SPDIF_VERSION_L 12
+#define SPDIF_VERSION_H 13
+
+#define SPDIF_REGNUM 14
+
+extern struct snd_soc_dai stmp3xxx_spdif_codec_dai;
+extern struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx;
+
+#endif /* __STMP3XXX_SPDIF_CODEC_H */
diff --git a/sound/soc/stmp3xxx/Kconfig b/sound/soc/stmp3xxx/Kconfig
new file mode 100644
index 000000000000..5a3ee6067f90
--- /dev/null
+++ b/sound/soc/stmp3xxx/Kconfig
@@ -0,0 +1,31 @@
+config SND_STMP3XXX_SOC
+ tristate "SoC Audio for the SigmaTel STMP3XXX chips"
+ depends on ARCH_STMP3XXX && SND_SOC
+ select SND_PCM
+ help
+ Say Y or M if you want to add support for codecs embedded into
+ the STMP3XXX chips.
+
+config SND_STMP3XXX_SOC_DAI
+ tristate
+
+config SND_STMP3XXX_SOC_SPDIF_DAI
+ tristate
+
+config SND_STMP3XXX_SOC_STMP3780_DEVB
+ tristate "SoC Audio support for STMP3780 Development Board"
+ depends on SND_STMP3XXX_SOC && ARCH_STMP378X
+ select SND_STMP3XXX_SOC_DAI
+ select SND_SOC_STMP378X_CODEC
+ help
+ Say Y if you want to add support for SoC audio on stmp3780 development
+ board with the stmp378x codec.
+
+config SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF
+ tristate "SoC SPDIF support for STMP3780 Development Board"
+ depends on SND_STMP3XXX_SOC && ARCH_STMP378X
+ select SND_STMP3XXX_SOC_SPDIF_DAI
+ select SND_SOC_STMP3XXX_SPDIF
+ help
+ Say Y if you want to add support for SoC audio on stmp3780 development
+ board with the SPDIF transmitter.
diff --git a/sound/soc/stmp3xxx/Makefile b/sound/soc/stmp3xxx/Makefile
new file mode 100644
index 000000000000..082e7aaf365c
--- /dev/null
+++ b/sound/soc/stmp3xxx/Makefile
@@ -0,0 +1,15 @@
+# STMP3XXX platfrom support
+snd-soc-stmp3xxx-objs := stmp3xxx_pcm.o
+snd-soc-stmp3xxx-dai-objs := stmp3xxx_dai.o
+snd-soc-stmp3xxx-spdif-dai-objs := stmp3xxx_spdif_dai.o
+
+obj-$(CONFIG_SND_STMP3XXX_SOC) += snd-soc-stmp3xxx.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_DAI) += snd-soc-stmp3xxx-dai.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_SPDIF_DAI) += snd-soc-stmp3xxx-spdif-dai.o
+
+# Machine Support
+snd-soc-stmp3780-devb-objs := stmp3780_devb.o
+snd-soc-stmp3780-devb-spdif-objs := stmp3780_devb_spdif.o
+
+obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB) += snd-soc-stmp3780-devb.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF) += snd-soc-stmp3780-devb-spdif.o
diff --git a/sound/soc/stmp3xxx/stmp3780_devb.c b/sound/soc/stmp3xxx/stmp3780_devb.c
new file mode 100644
index 000000000000..acb6414cc52f
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3780_devb.c
@@ -0,0 +1,97 @@
+/*
+ * ASoC driver for Freescale STMP3780 development board
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/regs-apbx.h>
+
+#include "../codecs/stmp378x_codec.h"
+#include "stmp3xxx_dai.h"
+#include "stmp3xxx_pcm.h"
+
+/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link stmp3780_devb_dai = {
+ .name = "STMP378X ADC/DAC",
+ .stream_name = "STMP378X ADC/DAC",
+ .cpu_dai = &stmp3xxx_adc_dai,
+ .codec_dai = &stmp378x_codec_dai,
+};
+
+/* stmp3780 devb audio machine driver */
+static struct snd_soc_machine snd_soc_machine_stmp3780_devb = {
+ .name = "STMP3780 Devb",
+ .dai_link = &stmp3780_devb_dai,
+ .num_links = 1,
+};
+
+/* stmp3780 devb audio subsystem */
+static struct snd_soc_device stmp3780_devb_snd_devdata = {
+ .machine = &snd_soc_machine_stmp3780_devb,
+ .platform = &stmp3xxx_soc_platform,
+ .codec_dev = &soc_codec_dev_stmp378x,
+};
+
+static struct platform_device *stmp3780_devb_snd_device;
+
+static int __init stmp3780_devb_init(void)
+{
+ int ret = 0;
+
+ stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!stmp3780_devb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(stmp3780_devb_snd_device,
+ &stmp3780_devb_snd_devdata);
+ stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev;
+ stmp3780_devb_snd_device->dev.platform_data =
+ &stmp3780_devb_snd_devdata;
+
+ if (ret) {
+ platform_device_put(stmp3780_devb_snd_device);
+ return ret;
+ }
+
+ ret = platform_device_add(stmp3780_devb_snd_device);
+ if (ret)
+ platform_device_put(stmp3780_devb_snd_device);
+
+ return ret;
+}
+
+static void __exit stmp3780_devb_exit(void)
+{
+ platform_device_unregister(stmp3780_devb_snd_device);
+}
+
+module_init(stmp3780_devb_init);
+module_exit(stmp3780_devb_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("STMP3780 development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3780_devb_spdif.c b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c
new file mode 100644
index 000000000000..c8dbe2cf6958
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c
@@ -0,0 +1,95 @@
+/*
+ * ASoC driver for STMP3780 development board
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/regs-apbx.h>
+
+#include <mach/stmp3xxx.h>
+
+#include "../codecs/stmp3xxx_spdif.h"
+#include "stmp3xxx_spdif_dai.h"
+#include "stmp3xxx_pcm.h"
+
+/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link stmp3780_devb_dai = {
+ .name = "STMP3XXX SPDIF",
+ .stream_name = "STMP3XXX SPDIF",
+ .cpu_dai = &stmp3xxx_spdif_dai,
+ .codec_dai = &stmp3xxx_spdif_codec_dai,
+};
+
+/* stmp3780 devb audio machine driver */
+static struct snd_soc_machine snd_soc_machine_stmp3780_devb = {
+ .name = "STMP3780 Devb",
+ .dai_link = &stmp3780_devb_dai,
+ .num_links = 1,
+};
+
+/* stmp3780 devb audio subsystem */
+static struct snd_soc_device stmp3780_devb_snd_devdata = {
+ .machine = &snd_soc_machine_stmp3780_devb,
+ .platform = &stmp3xxx_soc_platform,
+ .codec_dev = &soc_spdif_codec_dev_stmp3xxx,
+};
+
+static struct platform_device *stmp3780_devb_snd_device;
+
+static int __init stmp3780_devb_init(void)
+{
+ int ret = 0;
+
+ stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!stmp3780_devb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(stmp3780_devb_snd_device,
+ &stmp3780_devb_snd_devdata);
+ stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev;
+ stmp3780_devb_snd_device->dev.platform_data =
+ &stmp3780_devb_snd_devdata;
+
+ ret = platform_device_add(stmp3780_devb_snd_device);
+ if (ret)
+ goto out;
+
+ ret = spdif_pinmux_request();
+out:
+ if (ret)
+ platform_device_put(stmp3780_devb_snd_device);
+ return ret;
+}
+
+static void __exit stmp3780_devb_exit(void)
+{
+ spdif_pinmux_release();
+ platform_device_unregister(stmp3780_devb_snd_device);
+}
+
+module_init(stmp3780_devb_init);
+module_exit(stmp3780_devb_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("STMP3780 development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.c b/sound/soc/stmp3xxx/stmp3xxx_dai.c
new file mode 100644
index 000000000000..0fa7e1d89bf1
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_dai.c
@@ -0,0 +1,224 @@
+/*
+ * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+#include "stmp3xxx_pcm.h"
+
+#define STMP3XXX_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define STMP3XXX_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct stmp3xxx_pcm_dma_params stmp3xxx_audio_in = {
+ .name = "stmp3xxx adc",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 0,
+ .irq = IRQ_ADC_DMA,
+};
+
+struct stmp3xxx_pcm_dma_params stmp3xxx_audio_out = {
+ .name = "stmp3xxx dac",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 1,
+ .irq = IRQ_DAC_DMA,
+};
+
+static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = HW_AUDIOOUT_CTRL_RD();
+ underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ;
+ } else {
+ ctrl_reg = HW_AUDIOIN_CTRL_RD();
+ underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "%s underflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ HW_AUDIOOUT_CTRL_CLR(
+ BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ);
+ else
+ HW_AUDIOIN_CTRL_CLR(
+ BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ);
+
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "%s overflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ HW_AUDIOOUT_CTRL_CLR(
+ BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ);
+ else
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ);
+ } else
+ printk(KERN_WARNING "Unknown DAC error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int stmp3xxx_adc_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (playback)
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_RUN);
+ else
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_RUN);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (playback)
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_RUN);
+ else
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_RUN);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int stmp3xxx_adc_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int ret;
+
+ if (playback) {
+ irq = IRQ_DAC_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_audio_out;
+ } else {
+ irq = IRQ_ADC_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_audio_in;
+ }
+
+ ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx DAC/ADC Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n",
+ __func__, IRQ_DAC_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ);
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ);
+ HW_AUDIOOUT_CTRL_SET(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN);
+ } else {
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ);
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ);
+ HW_AUDIOIN_CTRL_SET(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN);
+ }
+
+ return 0;
+}
+
+static void stmp3xxx_adc_shutdown(struct snd_pcm_substream *substream)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ HW_AUDIOOUT_CTRL_CLR(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN);
+ free_irq(IRQ_DAC_ERROR, substream);
+ } else {
+ HW_AUDIOIN_CTRL_CLR(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN);
+ free_irq(IRQ_ADC_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_adc_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int stmp3xxx_adc_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define stmp3xxx_adc_suspend NULL
+#define stmp3xxx_adc_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai stmp3xxx_adc_dai = {
+ .name = "stmp3xxx adc/dac",
+ .id = 0,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = stmp3xxx_adc_suspend,
+ .resume = stmp3xxx_adc_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_ADC_RATES,
+ .formats = STMP3XXX_ADC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_ADC_RATES,
+ .formats = STMP3XXX_ADC_FORMATS,
+ },
+ .ops = {
+ .startup = stmp3xxx_adc_startup,
+ .shutdown = stmp3xxx_adc_shutdown,
+ .trigger = stmp3xxx_adc_trigger,
+ },
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_adc_dai);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("stmp3xxx dac/adc DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.h b/sound/soc/stmp3xxx/stmp3xxx_dai.h
new file mode 100644
index 000000000000..409256a86d15
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_dai.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_DEV_H
+#define _STMP3XXX_DEV_H
+extern struct snd_soc_dai stmp3xxx_adc_dai;
+#endif
diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.c b/sound/soc/stmp3xxx/stmp3xxx_pcm.c
new file mode 100644
index 000000000000..79fd5526fe6b
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.c
@@ -0,0 +1,440 @@
+/*
+ * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include <mach/regs-apbx.h>
+
+#include "stmp3xxx_pcm.h"
+
+static const struct snd_pcm_hardware stmp3xxx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 255,
+ .buffer_bytes_max = 64 * 1024,
+ .fifo_size = 32,
+};
+
+/*
+ * Required to request DMA channels
+ */
+struct device *stmp3xxx_pcm_dev;
+
+struct stmp3xxx_runtime_data {
+ u32 dma_ch;
+ u32 dma_period;
+ u32 dma_totsize;
+
+ struct stmp3xxx_pcm_dma_params *params;
+ struct stmp3xxx_dma_descriptor *dma_desc_array;
+};
+
+static irqreturn_t stmp3xxx_pcm_dma_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+
+#ifdef CONFIG_ARCH_STMP37XX
+ u32 err_mask = 1 << (16 + prtd->params->dma_ch);
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ u32 err_mask = 1 << prtd->params->dma_ch;
+#endif
+ u32 irq_mask = 1 << prtd->params->dma_ch;
+
+#ifdef CONFIG_ARCH_STMP37XX
+ if (HW_APBX_CTRL1_RD() & err_mask) {
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ if (HW_APBX_CTRL2_RD() & err_mask) {
+#endif
+ printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n",
+ __func__, prtd->params->dma_ch, prtd->params->name);
+#ifdef CONFIG_ARCH_STMP37XX
+ HW_APBX_CTRL1_CLR(err_mask);
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ HW_APBX_CTRL2_CLR(err_mask);
+#endif
+ } else if (HW_APBX_CTRL1_RD() & irq_mask) {
+ stmp3xxx_dma_clear_interrupt(prtd->dma_ch);
+ snd_pcm_period_elapsed(substream);
+ } else
+ printk(KERN_WARNING "%s: Unknown interrupt\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Make a circular DMA descriptor list
+ */
+static int stmp3xxx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ dma_addr_t dma_buffer_phys;
+ int periods_num, playback, i;
+
+ playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ periods_num = prtd->dma_totsize / prtd->dma_period;
+ dma_buffer_phys = runtime->dma_addr;
+
+ /* Reset DMA channel, enable interrupt */
+ stmp3xxx_dma_reset_channel(prtd->dma_ch);
+
+ /* Set up a DMA chain to sent DMA buffer */
+ for (i = 0; i < periods_num; i++) {
+ int next = (i + 1) % periods_num;
+ u32 cmd = 0;
+
+ /* Link with previous command */
+ prtd->dma_desc_array[i].command->next =
+ prtd->dma_desc_array[next].handle;
+
+ prtd->dma_desc_array[i].next_descr =
+ &prtd->dma_desc_array[next];
+
+ cmd = BF_APBX_CHn_CMD_XFER_COUNT(prtd->dma_period) |
+ BM_APBX_CHn_CMD_IRQONCMPLT |
+ BM_APBX_CHn_CMD_CHAIN;
+
+ /* Set DMA direction */
+ if (playback)
+ cmd |= BF_APBX_CHn_CMD_COMMAND(
+ BV_APBX_CHn_CMD_COMMAND__DMA_READ);
+ else
+ cmd |= BF_APBX_CHn_CMD_COMMAND(
+ BV_APBX_CHn_CMD_COMMAND__DMA_WRITE);
+
+ prtd->dma_desc_array[i].command->cmd = cmd;
+ prtd->dma_desc_array[i].command->buf_ptr = dma_buffer_phys;
+
+ /* Next data chunk */
+ dma_buffer_phys += prtd->dma_period;
+ }
+
+ return 0;
+}
+
+/*
+ * Stop circular DMA descriptor list
+ * We should not stop DMA in a middle of current transaction once we receive
+ * stop request from ALSA core. This function finds the next DMA descriptor
+ * and set it up to decrement DMA channel semaphore. So the current transaction
+ * is the last data transfer.
+ */
+static void stmp3xxx_pcm_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ dma_addr_t pos;
+ int desc;
+
+ /* Freez DMA channel for a moment */
+ stmp3xxx_dma_freeze(prtd->dma_ch);
+
+ /* Find current DMA descriptor */
+ pos = HW_APBX_CHn_BAR_RD(prtd->params->dma_ch);
+ desc = (pos - runtime->dma_addr) / prtd->dma_period;
+
+ /* Set up the next descriptor to decrement DMA channel sempahore */
+ prtd->dma_desc_array[desc].next_descr->command->cmd
+ = BM_APBX_CHn_CMD_SEMAPHORE;
+
+ /* Let the current DMA transaction finish */
+ stmp3xxx_dma_unfreeze(prtd->dma_ch);
+}
+
+static int stmp3xxx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+
+ case SNDRV_PCM_TRIGGER_START:
+ stmp3xxx_dma_go(prtd->dma_ch, prtd->dma_desc_array, 1);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ stmp3xxx_pcm_stop(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stmp3xxx_dma_unfreeze(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stmp3xxx_dma_freeze(prtd->dma_ch);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+stmp3xxx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ unsigned int offset;
+ dma_addr_t pos;
+
+ pos = HW_APBX_CHn_BAR_RD(prtd->params->dma_ch);
+ offset = bytes_to_frames(runtime, pos - runtime->dma_addr);
+
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int stmp3xxx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+
+ prtd->dma_period = params_period_bytes(hw_params);
+ prtd->dma_totsize = params_buffer_bytes(hw_params);
+
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int stmp3xxx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int stmp3xxx_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ struct stmp3xxx_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
+ int desc_num = stmp3xxx_pcm_hardware.periods_max;
+ int desc;
+ int ret;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ prtd->params = dma_data;
+ prtd->dma_ch = STMP3xxx_DMA(dma_data->dma_ch, dma_data->dma_bus);
+
+ ret = stmp3xxx_dma_request(prtd->dma_ch, stmp3xxx_pcm_dev,
+ prtd->params->name);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n",
+ __func__, dma_data->dma_bus, dma_data->dma_ch);
+ return ret;
+ }
+
+ /* Allocate memory for data and pio DMA descriptors */
+ prtd->dma_desc_array =
+ kzalloc(sizeof(struct stmp3xxx_dma_descriptor) * desc_num,
+ GFP_KERNEL);
+ if (prtd->dma_desc_array == NULL) {
+ printk(KERN_ERR "%s: Unable to allocate memory\n", __func__);
+ stmp3xxx_dma_release(prtd->dma_ch);
+ return -ENOMEM;
+ }
+
+ for (desc = 0; desc < desc_num; desc++) {
+ ret = stmp3xxx_dma_allocate_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ if (ret) {
+ printk(KERN_ERR"%s Unable to allocate DMA command %d\n",
+ __func__, desc);
+ goto err;
+ }
+ }
+
+ ret = request_irq(prtd->params->irq, stmp3xxx_pcm_dma_irq, 0,
+ "STMP3xxx PCM DMA", substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__,
+ prtd->params->irq);
+ goto err;
+ }
+
+
+ /* Enable completion interrupt */
+ stmp3xxx_dma_clear_interrupt(prtd->dma_ch);
+ stmp3xxx_dma_enable_interrupt(prtd->dma_ch);
+
+ return 0;
+
+err:
+ while (--desc >= 0)
+ stmp3xxx_dma_free_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ kfree(prtd->dma_desc_array);
+ stmp3xxx_dma_release(prtd->dma_ch);
+
+ return ret;
+}
+
+static int stmp3xxx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &stmp3xxx_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct stmp3xxx_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+
+ ret = stmp3xxx_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "stmp3xxx_pcm: Failed to request channels\n");
+ kfree(prtd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmp3xxx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ int desc_num = stmp3xxx_pcm_hardware.periods_max;
+ int desc;
+
+ /* Free DMA irq */
+ free_irq(prtd->params->irq, substream);
+
+ /* Free DMA channel */
+ for (desc = 0; desc < desc_num; desc++)
+ stmp3xxx_dma_free_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ kfree(prtd->dma_desc_array);
+ stmp3xxx_dma_release(prtd->dma_ch);
+
+ /* Free private runtime data */
+ kfree(prtd);
+
+ return 0;
+}
+
+static int stmp3xxx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+}
+
+struct snd_pcm_ops stmp3xxx_pcm_ops = {
+ .open = stmp3xxx_pcm_open,
+ .close = stmp3xxx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = stmp3xxx_pcm_hw_params,
+ .hw_free = stmp3xxx_pcm_hw_free,
+ .prepare = stmp3xxx_pcm_prepare,
+ .trigger = stmp3xxx_pcm_trigger,
+ .pointer = stmp3xxx_pcm_pointer,
+ .mmap = stmp3xxx_pcm_mmap,
+};
+
+static u64 stmp3xxx_pcm_dma_mask = DMA_32BIT_MASK;
+
+static int stmp3xxx_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ size_t size = stmp3xxx_pcm_hardware.buffer_bytes_max;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &stmp3xxx_pcm_dma_mask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_32BIT_MASK;
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL,
+ size, size);
+
+ return 0;
+}
+
+static void stmp3xxx_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+/*
+ * We need probe/remove callbacks to setup stmp3xxx_pcm_dev
+ */
+static int stmp3xxx_pcm_probe(struct platform_device *pdev)
+{
+ stmp3xxx_pcm_dev = &pdev->dev;
+ return 0;
+}
+
+static int stmp3xxx_pcm_remove(struct platform_device *pdev)
+{
+ stmp3xxx_pcm_dev = NULL;
+ return 0;
+}
+
+struct snd_soc_platform stmp3xxx_soc_platform = {
+ .name = "STMP3xxx Audio",
+ .pcm_ops = &stmp3xxx_pcm_ops,
+ .probe = stmp3xxx_pcm_probe,
+ .remove = stmp3xxx_pcm_remove,
+ .pcm_new = stmp3xxx_pcm_new,
+ .pcm_free = stmp3xxx_pcm_free,
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_soc_platform);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("STMP3xxx DMA Module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.h b/sound/soc/stmp3xxx/stmp3xxx_pcm.h
new file mode 100644
index 000000000000..78ac9487353f
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.h
@@ -0,0 +1,30 @@
+/*
+ * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_PCM_H
+#define _STMP3XXX_PCM_H
+
+struct stmp3xxx_pcm_dma_params {
+ char *name;
+ int dma_bus; /* DMA bus */
+ int dma_ch; /* DMA channel number */
+ int irq; /* DMA interrupt number */
+};
+
+extern struct snd_soc_platform stmp3xxx_soc_platform;
+
+#endif
diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c
new file mode 100644
index 000000000000..4529b3fcaf42
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c
@@ -0,0 +1,184 @@
+/*
+ * ALSA SoC SPDIF Audio Layer for STMP3xxx processor familiy
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/regs-spdif.h>
+#include "stmp3xxx_pcm.h"
+
+#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct stmp3xxx_pcm_dma_params stmp3xxx_spdif = {
+ .name = "stmp3xxx spdif",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 2,
+ .irq = IRQ_SPDIF_DMA,
+};
+
+static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg = 0;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = HW_SPDIF_CTRL_RD();
+ underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "underflow detected SPDIF\n");
+
+ if (playback)
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ);
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "overflow detected SPDIF\n");
+
+ if (playback)
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ);
+ } else
+ printk(KERN_WARNING "Unknown SPDIF error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int stmp3xxx_spdif_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (playback)
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_RUN);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (playback)
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_RUN);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int stmp3xxx_spdif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int ret;
+
+ if (playback) {
+ irq = IRQ_SPDIF_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_spdif;
+ }
+
+ ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx SPDIF Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n",
+ __func__, IRQ_SPDIF_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ);
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ);
+ HW_SPDIF_CTRL_SET(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN);
+ }
+
+ return 0;
+}
+
+static void stmp3xxx_spdif_shutdown(struct snd_pcm_substream *substream)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ HW_SPDIF_CTRL_CLR(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN);
+ free_irq(IRQ_SPDIF_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_spdif_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int stmp3xxx_spdif_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define stmp3xxx_spdif_suspend NULL
+#define stmp3xxx_spdif_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai stmp3xxx_spdif_dai = {
+ .name = "stmp3xxx spdif",
+ .id = 0,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = stmp3xxx_spdif_suspend,
+ .resume = stmp3xxx_spdif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .ops = {
+ .startup = stmp3xxx_spdif_startup,
+ .shutdown = stmp3xxx_spdif_shutdown,
+ .trigger = stmp3xxx_spdif_trigger,
+ },
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_spdif_dai);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("stmp3xxx SPDIF DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h
new file mode 100644
index 000000000000..bec3d6fad7c9
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter
+ *
+ * Author: Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_SPDIF_H
+#define _STMP3XXX_SPDIF_H
+extern struct snd_soc_dai stmp3xxx_spdif_dai;
+#endif