summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorLionel Xu <Lionel.Xu@freescale.com>2010-03-08 17:54:48 +0800
committerAlejandro Gonzalez <alex.gonzalez@digi.com>2010-05-25 11:17:18 +0200
commitf377b49771326a4b632088faa2267c7385294c60 (patch)
tree33e7f4d6d68af4eea312171f89433bf602f2bf90 /sound
parentc5d471596d41aa71765f7a4342b8cbcac685c4ad (diff)
ENGR00117750 MX28 ALSA:Support audio playback through SAIF
Porting from mxc sound asoc driver, to support audio playback on mx28 through saif Signed-off-by: Lionel Xu <r63889@freescale.com> Signed-off-by: Alejandro Gonzalez <alex.gonzalez@digi.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/codecs/sgtl5000.c14
-rw-r--r--sound/soc/mxs/Kconfig49
-rw-r--r--sound/soc/mxs/Makefile12
-rw-r--r--sound/soc/mxs/mxs-dai.c624
-rw-r--r--sound/soc/mxs/mxs-dai.h37
-rw-r--r--sound/soc/mxs/mxs-devb.c281
-rw-r--r--sound/soc/mxs/mxs-pcm.c425
-rw-r--r--sound/soc/mxs/mxs-pcm.h31
10 files changed, 1473 insertions, 2 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 09c88271e14a..ba22486b92a7 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -37,6 +37,7 @@ source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
source "sound/soc/imx/Kconfig"
source "sound/soc/stmp3xxx/Kconfig"
+source "sound/soc/mxs/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index ccb1fa6fe6b1..9545c564211d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -15,3 +15,4 @@ obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
obj-$(CONFIG_SND_SOC) += imx/
obj-$(CONFIG_SND_SOC) += stmp3xxx/
+obj-$(CONFIG_SND_SOC) += mxs/
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
index 1d5843e94bd0..f9f9a1a3b3fb 100644
--- a/sound/soc/codecs/sgtl5000.c
+++ b/sound/soc/codecs/sgtl5000.c
@@ -1,7 +1,7 @@
/*
* sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
*
- * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
*
* 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
@@ -14,6 +14,7 @@
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
+#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <sound/core.h>
@@ -655,11 +656,12 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
else
#endif
{
+ /*
if (!sgtl5000->master) {
pr_err("%s: PLL not supported in slave mode\n",
__func__);
return -EINVAL;
- }
+ }*/
clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT;
}
@@ -1050,9 +1052,11 @@ static int sgtl5000_probe(struct platform_device *pdev)
SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT;
sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, reg);
+#ifdef CONFIG_ARCH_MXC
if (cpu_is_mx25())
sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x01df);
else
+#endif
sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_ADC_CTRL);
@@ -1120,6 +1124,9 @@ static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
int ret = 0;
u32 val;
+ if (client->dev.platform_data)
+ clk_enable((struct clk *)client->dev.platform_data);
+
if (sgtl5000_codec) {
dev_err(&client->dev,
"Multiple SGTL5000 devices not supported\n");
@@ -1242,6 +1249,9 @@ static __devexit int sgtl5000_i2c_remove(struct i2c_client *client)
struct snd_soc_codec *codec = i2c_get_clientdata(client);
struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ if (client->dev.platform_data)
+ clk_disable((struct clk *)client->dev.platform_data);
+
snd_soc_unregister_dai(&sgtl5000_dai);
snd_soc_unregister_codec(codec);
diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig
new file mode 100644
index 000000000000..0e5bc1b052bb
--- /dev/null
+++ b/sound/soc/mxs/Kconfig
@@ -0,0 +1,49 @@
+config SND_MXS_SOC
+ tristate "SoC Audio for the MXS chips"
+ depends on ARCH_MXS && SND_SOC
+ select SND_PCM
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the MXS I2S or SSP interface.
+
+
+config SND_MXS_SOC_SPDIF_DAI
+ tristate
+
+config SND_MXS_SOC_EVK_DEVB
+ tristate "SoC Audio support for MXS-EVK SGTL5000"
+ depends on SND_MXS_SOC && ARCH_MX28
+ select SND_SOC_SGTL5000
+ help
+ Say Y if you want to add support for SoC audio on mx28 EVK development
+ board with the sgtl5000 codec.
+
+config SND_MXS_SOC_DAI
+ tristate "MXS Digital Audio Interface SAIF"
+ default y
+ depends on SND_MXS_SOC_EVK_DEVB
+ help
+ Enable MXS Digital Audio Interface SAIF
+
+config SND_MXS_SOC_SAIF0_SELECT
+ bool "Enable SAIF0 module"
+ default y
+ depends on SND_MXS_SOC_DAI
+ help
+ Enable MXS SAIF0 Module
+
+config SND_MXS_SOC_SAIF1_SELECT
+ bool "Enable SAIF1 module"
+ depends on SND_MXS_SOC_DAI
+ help
+ Enable MXS SAIF1 Module
+
+
+config SND_MXS_SOC_EVK_DEVB_SPDIF
+ tristate "SoC SPDIF support for MX28 EVK Development Board"
+ depends on SND_MXS_SOC && ARCH_MX28
+ select SND_MXS_SOC_SPDIF_DAI
+ select SND_SOC_MXS_SPDIF
+ help
+ Say Y if you want to add support for SoC audio on mx28 EVK development
+ board with the SPDIF transmitter.
diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile
new file mode 100644
index 000000000000..6a8ce0ef8d93
--- /dev/null
+++ b/sound/soc/mxs/Makefile
@@ -0,0 +1,12 @@
+# MXS platfrom support
+snd-soc-mxs-objs := mxs-pcm.o
+snd-soc-mxs-dai-objs := mxs-dai.o
+snd-soc-mxs-spdif-dai-objs := mxs-spdif-dai.o
+snd-soc-mxs-devb-objs := mxs-devb.o
+snd-soc-mxs-devb-spdif-objs := mxs-devb-spdif.o
+
+obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o
+obj-$(CONFIG_SND_MXS_SOC_DAI) += snd-soc-mxs-dai.o
+obj-$(CONFIG_SND_MXS_SOC_SPDIF_DAI) += snd-soc-mxs-spdif-dai.o
+obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB) += snd-soc-mxs-devb.o
+obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB_SPDIF) += snd-soc-mxs-devb-spdif.o
diff --git a/sound/soc/mxs/mxs-dai.c b/sound/soc/mxs/mxs-dai.c
new file mode 100644
index 000000000000..de759b5face4
--- /dev/null
+++ b/sound/soc/mxs/mxs-dai.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include <mach/mx28.h>
+
+#include "mxs-pcm.h"
+#include "mxs-dai.h"
+
+#define SAIF0_CTRL (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF1_CTRL (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF0_CTRL_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_SET)
+#define SAIF1_CTRL_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_SET)
+#define SAIF0_CTRL_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_CLR)
+#define SAIF1_CTRL_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_CLR)
+
+#define SAIF0_STAT (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT)
+#define SAIF1_STAT (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT)
+#define SAIF0_STAT_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_SET)
+#define SAIF1_STAT_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_SET)
+#define SAIF0_STAT_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_CLR)
+#define SAIF1_STAT_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_CLR)
+
+#define SAIF0_DATA (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA)
+#define SAIF1_DATA (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA)
+#define SAIF0_DATA_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_SET)
+#define SAIF1_DATA_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_SET)
+#define SAIF0_DATA_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_CLR)
+#define SAIF1_DATA_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_CLR)
+
+#define SAIF0_VERSION (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_VERSION)
+#define SAIF1_VERSION (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_VERSION)
+
+#define HW_SAIF_CTRL (0x00000000)
+#define HW_SAIF_CTRL_SET (0x00000004)
+#define HW_SAIF_CTRL_CLR (0x00000008)
+#define HW_SAIF_CTRL_TOG (0x0000000c)
+
+#define BM_SAIF_CTRL_SFTRST 0x80000000
+#define BM_SAIF_CTRL_CLKGATE 0x40000000
+#define BP_SAIF_CTRL_BITCLK_MULT_RATE 27
+#define BM_SAIF_CTRL_BITCLK_MULT_RATE 0x38000000
+#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \
+ (((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE)
+#define BM_SAIF_CTRL_BITCLK_BASE_RATE 0x04000000
+#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN 0x02000000
+#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN 0x01000000
+#define BP_SAIF_CTRL_RSRVD2 21
+#define BM_SAIF_CTRL_RSRVD2 0x00E00000
+#define BF_SAIF_CTRL_RSRVD2(v) \
+ (((v) << 21) & BM_SAIF_CTRL_RSRVD2)
+#define BP_SAIF_CTRL_DMAWAIT_COUNT 16
+#define BM_SAIF_CTRL_DMAWAIT_COUNT 0x001F0000
+#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \
+ (((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT)
+#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14
+#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000
+#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \
+ (((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT)
+#define BM_SAIF_CTRL_LRCLK_PULSE 0x00002000
+#define BM_SAIF_CTRL_BIT_ORDER 0x00001000
+#define BM_SAIF_CTRL_DELAY 0x00000800
+#define BM_SAIF_CTRL_JUSTIFY 0x00000400
+#define BM_SAIF_CTRL_LRCLK_POLARITY 0x00000200
+#define BM_SAIF_CTRL_BITCLK_EDGE 0x00000100
+#define BP_SAIF_CTRL_WORD_LENGTH 4
+#define BM_SAIF_CTRL_WORD_LENGTH 0x000000F0
+#define BF_SAIF_CTRL_WORD_LENGTH(v) \
+ (((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH)
+#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE 0x00000008
+#define BM_SAIF_CTRL_SLAVE_MODE 0x00000004
+#define BM_SAIF_CTRL_READ_MODE 0x00000002
+#define BM_SAIF_CTRL_RUN 0x00000001
+
+#define HW_SAIF_STAT (0x00000010)
+#define HW_SAIF_STAT_SET (0x00000014)
+#define HW_SAIF_STAT_CLR (0x00000018)
+#define HW_SAIF_STAT_TOG (0x0000001c)
+
+#define BM_SAIF_STAT_PRESENT 0x80000000
+#define BP_SAIF_STAT_RSRVD2 17
+#define BM_SAIF_STAT_RSRVD2 0x7FFE0000
+#define BF_SAIF_STAT_RSRVD2(v) \
+ (((v) << 17) & BM_SAIF_STAT_RSRVD2)
+#define BM_SAIF_STAT_DMA_PREQ 0x00010000
+#define BP_SAIF_STAT_RSRVD1 7
+#define BM_SAIF_STAT_RSRVD1 0x0000FF80
+#define BF_SAIF_STAT_RSRVD1(v) \
+ (((v) << 7) & BM_SAIF_STAT_RSRVD1)
+#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040
+#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ 0x00000020
+#define BM_SAIF_STAT_FIFO_SERVICE_IRQ 0x00000010
+#define BP_SAIF_STAT_RSRVD0 1
+#define BM_SAIF_STAT_RSRVD0 0x0000000E
+#define BF_SAIF_STAT_RSRVD0(v) \
+ (((v) << 1) & BM_SAIF_STAT_RSRVD0)
+#define BM_SAIF_STAT_BUSY 0x00000001
+
+#define HW_SAIF_DATA (0x00000020)
+#define HW_SAIF_DATA_SET (0x00000024)
+#define HW_SAIF_DATA_CLR (0x00000028)
+#define HW_SAIF_DATA_TOG (0x0000002c)
+
+#define BP_SAIF_DATA_PCM_RIGHT 16
+#define BM_SAIF_DATA_PCM_RIGHT 0xFFFF0000
+#define BF_SAIF_DATA_PCM_RIGHT(v) \
+ (((v) << 16) & BM_SAIF_DATA_PCM_RIGHT)
+#define BP_SAIF_DATA_PCM_LEFT 0
+#define BM_SAIF_DATA_PCM_LEFT 0x0000FFFF
+#define BF_SAIF_DATA_PCM_LEFT(v) \
+ (((v) << 0) & BM_SAIF_DATA_PCM_LEFT)
+
+#define HW_SAIF_VERSION (0x00000030)
+
+#define BP_SAIF_VERSION_MAJOR 24
+#define BM_SAIF_VERSION_MAJOR 0xFF000000
+#define BF_SAIF_VERSION_MAJOR(v) \
+ (((v) << 24) & BM_SAIF_VERSION_MAJOR)
+#define BP_SAIF_VERSION_MINOR 16
+#define BM_SAIF_VERSION_MINOR 0x00FF0000
+#define BF_SAIF_VERSION_MINOR(v) \
+ (((v) << 16) & BM_SAIF_VERSION_MINOR)
+#define BP_SAIF_VERSION_STEP 0
+#define BM_SAIF_VERSION_STEP 0x0000FFFF
+#define BF_SAIF_VERSION_STEP(v) \
+ (((v) << 0) & BM_SAIF_VERSION_STEP)
+/* debug */
+#define MXS_SAIF_DEBUG 0
+#if MXS_SAIF_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+#define MXS_SAIF_DUMP 0
+#if MXS_SAIF_DUMP
+#define SAIF_DUMP() \
+ do { \
+ printk(KERN_INFO "dump @ %s\n", __func__);\
+ printk(KERN_INFO "scr %x\t, %x\n", \
+ __raw_readl(SAIF0_CTRL), __raw_readl(SAIF1_CTRL));\
+ printk(KERN_INFO "stat %x\t, %x\n", \
+ __raw_readl(SAIF0_STAT), __raw_readl(SAIF1_STAT));\
+ printk(KERN_INFO "data %x\t, %x\n", \
+ __raw_readl(SAIF0_DATA), __raw_readl(SAIF1_DATA));\
+ printk(KERN_INFO "version %x\t, %x\n", \
+ __raw_readl(SAIF0_VERSION), __raw_readl(SAIF1_VERSION));
+ } while (0);
+#else
+#define SAIF_DUMP()
+#endif
+
+#define SAIF0_PORT 0
+#define SAIF1_PORT 1
+
+#define MXS_DAI_SAIF0 0
+#define MXS_DAI_SAIF1 1
+
+static int saif_active[2] = { 0, 0 };
+
+struct mxs_pcm_dma_params mxs_saif_0 = {
+ .name = "mxs-saif-0",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF0,
+ .irq = IRQ_SAIF0_DMA,
+};
+
+struct mxs_pcm_dma_params mxs_saif_1 = {
+ .name = "mxs-saif-1",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF1,
+ .irq = IRQ_SAIF1_DMA,
+};
+
+/*
+* SAIF system clock configuration.
+* Should only be called when port is inactive.
+*/
+static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 scr;
+ struct clk *saif_clk;
+
+ if (cpu_dai->id == MXS_DAI_SAIF0) {
+ scr = __raw_readl(SAIF0_CTRL);
+ saif_clk = clk_get(NULL, "saif.0");
+ } else {
+ scr = __raw_readl(SAIF1_CTRL);
+ saif_clk = clk_get(NULL, "saif.1");
+ }
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ clk_set_rate(saif_clk, freq);
+ clk_enable(saif_clk);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * SAIF Clock dividers
+ * Should only be called when port is inactive.
+ */
+static int mxs_saif_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ return 0;
+}
+
+/*
+ * SAIF DAI format configuration.
+ * Should only be called when port is inactive.
+ * Note: We don't use the I2S modes but instead manually configure the
+ * SAIF for I2S.
+ */
+static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ u32 scr, stat;
+
+ if (cpu_dai->id == MXS_DAI_SAIF0) {
+ scr = __raw_readl(SAIF0_CTRL);
+ stat = __raw_readl(SAIF0_STAT);
+ } else {
+ scr = __raw_readl(SAIF1_CTRL);
+ stat = __raw_readl(SAIF1_STAT);
+ }
+
+ if (stat & BM_SAIF_STAT_BUSY)
+ return 0;
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data frame low 1clk before data */
+ scr |= BM_SAIF_CTRL_DELAY;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data frame high with data */
+ scr &= ~BM_SAIF_CTRL_DELAY;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ scr &= ~BM_SAIF_CTRL_JUSTIFY;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+ scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+ scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ scr &= ~BM_SAIF_CTRL_SLAVE_MODE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ scr |= BM_SAIF_CTRL_SLAVE_MODE;
+ break;
+ }
+
+
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ __raw_writel(scr, SAIF0_CTRL);
+ else
+ __raw_writel(scr, SAIF1_CTRL);
+
+ SAIF_DUMP();
+ return 0;
+}
+
+static int mxs_saif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ /* we cant really change any saif values after saif is enabled
+ * need to fix in software for max flexibility - lrg */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return 0;
+
+ /* reset the SAIF port - Sect 45.4.4 */
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ if (saif_active[SAIF0_PORT]++)
+ return 0;
+ if (cpu_dai->id == MXS_DAI_SAIF1)
+ if (saif_active[SAIF1_PORT]++)
+ return 0;
+
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ cpu_dai->dma_data = &mxs_saif_0;
+ else
+ cpu_dai->dma_data = &mxs_saif_1;
+ SAIF_DUMP();
+ return 0;
+}
+
+/*
+ * Should only be called when port is inactive.
+ * although can be called multiple times by upper layers.
+ */
+static int mxs_saif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ int id;
+ u32 scr, stat;
+ id = cpu_dai->id;
+
+ if (cpu_dai->id == MXS_DAI_SAIF0) {
+ scr = __raw_readl(SAIF0_CTRL);
+ stat = __raw_readl(SAIF0_STAT);
+ } else {
+ scr = __raw_readl(SAIF1_CTRL);
+ stat = __raw_readl(SAIF1_STAT);
+ }
+
+ /* cant change any parameters when SAIF is running */
+ /* DAI data (word) size */
+ scr &= ~BM_SAIF_CTRL_WORD_LENGTH;
+ scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(0);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(4);
+ scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(8);
+ scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ break;
+ }
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* enable TX mode */
+ scr &= ~BM_SAIF_CTRL_READ_MODE;
+ } else {
+ /* enable RX mode */
+ scr |= BM_SAIF_CTRL_READ_MODE;
+ }
+
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ __raw_writel(scr, SAIF0_CTRL);
+ else
+ __raw_writel(scr, SAIF1_CTRL);
+
+ return 0;
+}
+
+static int mxs_saif_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF0_CTRL_CLR);
+ else
+ __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF1_CTRL_CLR);
+
+ /* enable the saif port, note that no other port config
+ * should happen after SSIEN is set */
+ SAIF_DUMP();
+ return 0;
+}
+
+static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ u32 scr;
+ if (cpu_dai->id == MXS_DAI_SAIF0)
+ scr = __raw_readl(SAIF0_CTRL);
+ else
+ scr = __raw_readl(SAIF1_CTRL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*write a data to saif data register to trigger the transfer*/
+ __raw_writel(0x0, SAIF0_DATA);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ scr &= ~BM_SAIF_CTRL_RUN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ SAIF_DUMP();
+ return 0;
+}
+
+static void mxs_saif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ int id;
+
+ id = cpu_dai->id;
+
+ /* shutdown SAIF if neither Tx or Rx is active */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return;
+
+ if (id == MXS_DAI_SAIF0) {
+ if (--saif_active[SAIF0_PORT] > 1)
+ return;
+ } else {
+ if (--saif_active[SAIF1_PORT])
+ return;
+ }
+}
+
+#ifdef CONFIG_PM
+static int mxs_saif_suspend(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+ /* do we need to disable any clocks? */
+ return 0;
+}
+
+static int mxs_saif_resume(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+ /* do we need to enable any clocks? */
+ return 0;
+}
+#else
+#define mxs_saif_suspend NULL
+#define mxs_saif_resume NULL
+#endif
+
+static int fifo_err_counter;
+
+static irqreturn_t saif0_irq(int irq, void *dev_id)
+{
+ if (fifo_err_counter++ % 100 == 0)
+ printk(KERN_ERR "saif0_irq SAIF_STAT %x SAIF_CTRL %x fifo_errs=\
+ %d\n",
+ __raw_readl(SAIF0_STAT),
+ __raw_readl(SAIF0_CTRL),
+ fifo_err_counter);
+ __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \
+ BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF0_STAT_CLR);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t saif1_irq(int irq, void *dev_id)
+{
+ if (fifo_err_counter++ % 100 == 0)
+ printk(KERN_ERR "saif1_irq SAIF_STAT %x SAIF_CTRL %x \
+ fifo_errs=%d\n",
+ __raw_readl(SAIF1_STAT),
+ __raw_readl(SAIF1_CTRL),
+ fifo_err_counter);
+ __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \
+ BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF1_STAT_CLR);
+ return IRQ_HANDLED;
+}
+
+static int mxs_saif_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ printk("in mxs_saif_probe\n");
+ if (!strcmp(dai->name, "mxs-saif-0"))
+ if (request_irq(IRQ_SAIF0, saif0_irq, 0, "saif0", dai)) {
+ printk(KERN_ERR "%s: failure requesting irq %s\n",
+ __func__, "saif0");
+ return -EBUSY;
+ }
+
+ if (!strcmp(dai->name, "mxs-saif-1"))
+ if (request_irq(IRQ_SAIF1, saif1_irq, 0, "saif1", dai)) {
+ printk(KERN_ERR "%s: failure requesting irq %s\n",
+ __func__, "saif1");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void mxs_saif_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ if (!strcmp(dai->name, "mxs-saif-0"))
+ free_irq(IRQ_SAIF0, dai);
+
+ if (!strcmp(dai->name, "mxs-saif-1"))
+ free_irq(IRQ_SAIF1, dai);
+}
+
+#define MXS_SAIF_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define MXS_SAIF_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops mxs_saif_dai_ops = {
+ .startup = mxs_saif_startup,
+ .shutdown = mxs_saif_shutdown,
+ .trigger = mxs_saif_trigger,
+ .prepare = mxs_saif_prepare,
+ .hw_params = mxs_saif_hw_params,
+ .set_sysclk = mxs_saif_set_dai_sysclk,
+ .set_clkdiv = mxs_saif_set_dai_clkdiv,
+ .set_fmt = mxs_saif_set_dai_fmt,
+};
+
+struct snd_soc_dai mxs_saif_dai[] = {
+ {
+ .name = "mxs-saif-0",
+ .id = MXS_DAI_SAIF0,
+ .probe = mxs_saif_probe,
+ .remove = mxs_saif_remove,
+ .suspend = mxs_saif_suspend,
+ .resume = mxs_saif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .ops = &mxs_saif_dai_ops,
+ },
+ {
+ .name = "mxs-saif-1",
+ .id = MXS_DAI_SAIF1,
+ .probe = mxs_saif_probe,
+ .remove = mxs_saif_remove,
+ .suspend = mxs_saif_suspend,
+ .resume = mxs_saif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .ops = &mxs_saif_dai_ops,
+ }
+};
+EXPORT_SYMBOL_GPL(mxs_saif_dai);
+
+static int __init mxs_saif_init(void)
+{
+ return snd_soc_register_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai));
+}
+
+static void __exit mxs_saif_exit(void)
+{
+ snd_soc_unregister_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai));
+}
+
+module_init(mxs_saif_init);
+module_exit(mxs_saif_exit);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX28 ASoC I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-dai.h b/sound/soc/mxs/mxs-dai.h
new file mode 100644
index 000000000000..93b4038c5ca9
--- /dev/null
+++ b/sound/soc/mxs/mxs-dai.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MXS_SAIF_H
+#define _MXS_SAIF_H
+
+#include <mach/hardware.h>
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK 0
+
+
+/* SSI Div 2 */
+#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2)
+#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2
+
+#define IMX_DAI_AC97_1 0
+#define IMX_DAI_AC97_2 1
+
+extern struct snd_soc_dai mxs_saif_dai[];
+
+#endif
diff --git a/sound/soc/mxs/mxs-devb.c b/sound/soc/mxs/mxs-devb.c
new file mode 100644
index 000000000000..e92f99b1ae95
--- /dev/null
+++ b/sound/soc/mxs/mxs-devb.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, 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/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/clk.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 <mach/dma.h>
+#include <mach/device.h>
+
+#include "mxs-dai.h"
+#include "mxs-pcm.h"
+#include "../codecs/sgtl5000.h"
+
+/* SAIF BCLK and LRC master */
+#define SGTL5000_SAIF_MASTER 0
+
+struct mxs_evk_priv {
+ int sysclk;
+ int hw;
+ struct platform_device *pdev;
+};
+
+static struct mxs_evk_priv card_priv;
+
+static int mxs_evk_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct mxs_evk_priv *priv = &card_priv;
+ unsigned int rate = params_rate(params);
+ int ret = 0;
+
+ u32 dai_format;
+
+ /* only need to do this once as capture and playback are sync */
+ if (priv->hw)
+ return 0;
+ priv->hw = 1;
+ priv->sysclk = 512 * rate;
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, priv->sysclk, 0);
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, rate, 0);
+
+#if SGTL5000_SAIF_MASTER
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+#else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+#endif
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SAIF system clock as output */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, priv->sysclk, \
+ SND_SOC_CLOCK_OUT);
+
+ return 0;
+}
+
+static int mxs_evk_startup(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static void mxs_evk_shutdown(struct snd_pcm_substream *substream)
+{
+ struct mxs_evk_priv *priv = &card_priv;
+ priv->hw = 0;
+}
+
+/*
+ * mxs_evk SGTL5000 audio DAI opserations.
+ */
+static struct snd_soc_ops mxs_evk_ops = {
+ .startup = mxs_evk_startup,
+ .shutdown = mxs_evk_shutdown,
+ .hw_params = mxs_evk_audio_hw_params,
+};
+
+/* mxs_evk machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MIC_IN", NULL, "Mic Jack"},
+
+ /* Line in Jack --> LINE_IN */
+ {"LINE_IN", NULL, "Line In Jack"},
+
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HP_OUT"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const char *line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+ SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+/* mxs_evk card dapm widgets */
+static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static int mxs_evk_sgtl5000_init(struct snd_soc_codec *codec)
+{
+ /* Add mxs_evk specific widgets */
+ snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets,
+ ARRAY_SIZE(mxs_evk_dapm_widgets));
+
+ /* Set up mxs_evk specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_disable_pin(codec, "Line In Jack");
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* mxs_evk digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link mxs_evk_dai = {
+ .name = "SGTL5000",
+ .stream_name = "SGTL5000",
+ .codec_dai = &sgtl5000_dai,
+ .init = mxs_evk_sgtl5000_init,
+ .ops = &mxs_evk_ops,
+};
+
+static int mxs_evk_card_remove(struct platform_device *pdev)
+{
+ struct mxs_evk_priv *priv = &card_priv;
+ struct mxs_audio_platform_data *plat;
+ if (priv->pdev) {
+ plat = priv->pdev->dev.platform_data;
+ if (plat->finit)
+ plat->finit();
+ }
+
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_mxs_evk = {
+ .name = "mxs-evk",
+ .platform = &mxs_soc_platform,
+ .dai_link = &mxs_evk_dai,
+ .num_links = 1,
+ .remove = mxs_evk_card_remove,
+};
+
+static struct snd_soc_device mxs_evk_snd_devdata = {
+ .card = &snd_soc_card_mxs_evk,
+ .codec_dev = &soc_codec_dev_sgtl5000,
+};
+
+static int __devinit mxs_evk_sgtl5000_probe(struct platform_device *pdev)
+{
+ struct mxs_audio_platform_data *plat = pdev->dev.platform_data;
+
+ int ret = -EINVAL;
+ /*init the clk*/
+ if (plat->init && plat->init())
+ goto err_plat_init;
+
+ if (plat->saif0_select == 1)
+ mxs_evk_dai.cpu_dai = &mxs_saif_dai[0];
+ else
+ mxs_evk_dai.cpu_dai = &mxs_saif_dai[1];
+ return 0;
+err_plat_init:
+ if (plat->finit)
+ plat->finit();
+ return ret;
+}
+
+static int mxs_evk_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct mxs_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->finit)
+ plat->finit();
+ return 0;
+}
+
+static struct platform_driver mxs_evk_sgtl5000_audio_driver = {
+ .probe = mxs_evk_sgtl5000_probe,
+ .remove = mxs_evk_sgtl5000_remove,
+ .driver = {
+ .name = "mxs-sgtl5000",
+ },
+};
+
+static struct platform_device *mxs_evk_snd_device;
+
+static int __init mxs_evk_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&mxs_evk_sgtl5000_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ mxs_evk_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!mxs_evk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mxs_evk_snd_device, &mxs_evk_snd_devdata);
+ mxs_evk_snd_devdata.dev = &mxs_evk_snd_device->dev;
+ ret = platform_device_add(mxs_evk_snd_device);
+
+ if (ret)
+ platform_device_put(mxs_evk_snd_device);
+
+ return ret;
+}
+
+static void __exit mxs_evk_exit(void)
+{
+ platform_driver_unregister(&mxs_evk_sgtl5000_audio_driver);
+ platform_device_unregister(mxs_evk_snd_device);
+}
+
+module_init(mxs_evk_init);
+module_exit(mxs_evk_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SGTL5000 Driver for MXS EVK");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c
new file mode 100644
index 000000000000..2bb58b5ba586
--- /dev/null
+++ b/sound/soc/mxs/mxs-pcm.c
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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 <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/dmaengine.h>
+
+#include "mxs-pcm.h"
+static const struct snd_pcm_hardware mxs_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 *mxs_pcm_dev;
+
+struct mxs_runtime_data {
+ u32 dma_ch;
+ u32 dma_period;
+ u32 dma_totsize;
+
+ struct mxs_pcm_dma_params *params;
+ struct mxs_dma_desc *dma_desc_array[255];
+};
+
+static irqreturn_t mxs_pcm_dma_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct mxs_runtime_data *prtd = substream->runtime->private_data;
+ struct mxs_dma_info dma_info;
+
+ mxs_dma_get_info(prtd->dma_ch, &dma_info);
+
+ if (dma_info.status) {
+ printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n",
+ __func__, prtd->params->dma_ch, prtd->params->name);
+ mxs_dma_ack_irq(prtd->dma_ch);
+ } else {
+ mxs_dma_ack_irq(prtd->dma_ch);
+ snd_pcm_period_elapsed(substream);
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * Make a circular DMA descriptor list
+ */
+static int mxs_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_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 */
+ mxs_dma_reset(prtd->dma_ch);
+
+ /* Set up a DMA chain to sent DMA buffer */
+ for (i = 0; i < periods_num; i++) {
+ int ret;
+ /* Link with previous command */
+ prtd->dma_desc_array[i]->cmd.cmd.bits.bytes = prtd->dma_period;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.irq = 1;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.chain = 1;
+ /* Set DMA direction */
+ if (playback)
+ prtd->dma_desc_array[i]->cmd.cmd.bits.command = \
+ DMA_READ;
+ else
+ prtd->dma_desc_array[i]->cmd.cmd.bits.command = \
+ DMA_WRITE;
+
+ prtd->dma_desc_array[i]->cmd.address = dma_buffer_phys;
+
+ ret = mxs_dma_desc_append(prtd->dma_ch, \
+ prtd->dma_desc_array[i]);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to append DMA descriptor\n",
+ __func__);
+ return ret;
+ }
+ /* 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 mxs_pcm_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_dma_info dma_info;
+ int desc;
+
+ /* Freez DMA channel for a moment */
+ mxs_dma_freeze(prtd->dma_ch);
+ mxs_dma_get_info(prtd->dma_ch, &dma_info);
+
+ desc = (dma_info.buf_addr - runtime->dma_addr) / prtd->dma_period;
+
+ /* Set up the next descriptor to decrement DMA channel sempahore */
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.bytes = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.pio_words = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.dec_sem = 1;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.irq = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.command = NO_DMA_XFER;
+
+ mxs_dma_unfreeze(prtd->dma_ch);
+}
+
+static int mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct mxs_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+ switch (cmd) {
+
+ case SNDRV_PCM_TRIGGER_START:
+ mxs_dma_enable(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ mxs_pcm_stop(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ mxs_dma_unfreeze(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ mxs_dma_freeze(prtd->dma_ch);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+mxs_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_dma_info dma_info;
+ unsigned int offset;
+ dma_addr_t pos;
+
+ mxs_dma_get_info(prtd->params->dma_ch, &dma_info);
+ pos = dma_info.buf_addr;
+
+ offset = bytes_to_frames(runtime, pos - runtime->dma_addr);
+
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int mxs_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct mxs_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 mxs_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int mxs_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 mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
+ int desc_num = mxs_pcm_hardware.periods_max;
+ int desc;
+ int ret;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ prtd->params = dma_data;
+ prtd->dma_ch = dma_data->dma_ch;
+
+ ret = mxs_dma_request(prtd->dma_ch, mxs_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 */
+ for (desc = 0; desc < desc_num; desc++) {
+ prtd->dma_desc_array[desc] = mxs_dma_alloc_desc();
+ if (prtd->dma_desc_array[desc] == NULL) {
+ printk(KERN_ERR"%s Unable to allocate DMA command %d\n",
+ __func__, desc);
+ goto err;
+ }
+ }
+
+ ret = request_irq(prtd->params->irq, mxs_pcm_dma_irq, 0,
+ "MXS 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 */
+ mxs_dma_ack_irq(prtd->dma_ch);
+ mxs_dma_enable_irq(prtd->dma_ch, 1);
+
+ return 0;
+
+err:
+ while (--desc >= 0)
+ mxs_dma_free_desc(prtd->dma_desc_array[desc]);
+ mxs_dma_release(prtd->dma_ch, mxs_pcm_dev);
+
+ return ret;
+}
+
+static int mxs_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd;
+ int ret;
+
+ /* Ensure that buffer size is a multiple of the period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &mxs_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct mxs_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+
+ ret = mxs_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "mxs_pcm: Failed to request channels\n");
+ kfree(prtd);
+ return ret;
+ }
+ return 0;
+}
+
+static int mxs_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ int desc_num = mxs_pcm_hardware.periods_max;
+ int desc;
+
+ static LIST_HEAD(list);
+ mxs_dma_disable(prtd->dma_ch);
+ /* Free DMA irq */
+ free_irq(prtd->params->irq, substream);
+ mxs_dma_get_cooked(prtd->dma_ch, &list);
+ /* Free DMA channel*/
+ for (desc = 0; desc < desc_num; desc++)
+ mxs_dma_free_desc(prtd->dma_desc_array[desc]);
+ mxs_dma_release(prtd->dma_ch, mxs_pcm_dev);
+
+ /* Free private runtime data */
+ kfree(prtd);
+ return 0;
+}
+
+static int mxs_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 mxs_pcm_ops = {
+ .open = mxs_pcm_open,
+ .close = mxs_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = mxs_pcm_hw_params,
+ .hw_free = mxs_pcm_hw_free,
+ .prepare = mxs_pcm_prepare,
+ .trigger = mxs_pcm_trigger,
+ .pointer = mxs_pcm_pointer,
+ .mmap = mxs_pcm_mmap,
+};
+
+static u64 mxs_pcm_dma_mask = DMA_BIT_MASK(32);
+
+static int mxs_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ size_t size = mxs_pcm_hardware.buffer_bytes_max;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &mxs_pcm_dma_mask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL,
+ size, size);
+
+ return 0;
+}
+
+static void mxs_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+/*
+ * We need probe/remove callbacks to setup mxs_pcm_dev
+ */
+static int mxs_pcm_probe(struct platform_device *pdev)
+{
+ mxs_pcm_dev = &pdev->dev;
+ return 0;
+}
+
+static int mxs_pcm_remove(struct platform_device *pdev)
+{
+ mxs_pcm_dev = NULL;
+ return 0;
+}
+
+struct snd_soc_platform mxs_soc_platform = {
+ .name = "MXS Audio",
+ .pcm_ops = &mxs_pcm_ops,
+ .probe = mxs_pcm_probe,
+ .remove = mxs_pcm_remove,
+ .pcm_new = mxs_pcm_new,
+ .pcm_free = mxs_pcm_free,
+};
+EXPORT_SYMBOL_GPL(mxs_soc_platform);
+
+static int __init mxs_pcm_init(void)
+{
+ return snd_soc_register_platform(&mxs_soc_platform);
+}
+
+static void __exit mxs_pcm_exit(void)
+{
+ snd_soc_unregister_platform(&mxs_soc_platform);
+}
+module_init(mxs_pcm_init);
+module_exit(mxs_pcm_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXS DMA Module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h
new file mode 100644
index 000000000000..08a9d92e2afe
--- /dev/null
+++ b/sound/soc/mxs/mxs-pcm.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MXS_PCM_H
+#define _MXS_PCM_H
+
+struct mxs_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 mxs_soc_platform;
+
+#endif