summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-mx28/clock.c141
-rw-r--r--arch/arm/mach-mx28/device.c77
-rw-r--r--arch/arm/mach-mx28/mx28evk.c12
-rw-r--r--arch/arm/mach-mx28/mx28evk_pins.c43
-rw-r--r--arch/arm/plat-mxs/device.c21
-rw-r--r--arch/arm/plat-mxs/include/mach/device.h18
-rw-r--r--drivers/i2c/busses/i2c-mxs.c1
-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
17 files changed, 1784 insertions, 4 deletions
diff --git a/arch/arm/mach-mx28/clock.c b/arch/arm/mach-mx28/clock.c
index fd4695e4a7d4..e7ab14dc4213 100644
--- a/arch/arm/mach-mx28/clock.c
+++ b/arch/arm/mach-mx28/clock.c
@@ -28,7 +28,14 @@
#include "regs-clkctrl.h"
#include "regs-digctl.h"
-
+#define HW_SAIF_CTRL (0x00000000)
+#define HW_SAIF_STAT (0x00000010)
+#define SAIF0_CTRL (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF0_STAT (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT)
+#define SAIF1_CTRL (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF1_STAT (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT)
+#define BM_SAIF_CTRL_RUN 0x00000001
+#define BM_SAIF_STAT_BUSY 0x00000001
#define CLKCTRL_BASE_ADDR IO_ADDRESS(CLKCTRL_PHYS_ADDR)
#define DIGCTRL_BASE_ADDR IO_ADDRESS(DIGCTL_PHYS_ADDR)
@@ -873,7 +880,7 @@ static int ssp_set_rate(struct clk *clk, unsigned long rate)
out:
if (ret != 0)
- printk(KERN_ERR "%s: error %d\n", __func__, ret);
+ pr_err("%s: error %d\n", __func__, ret);
return ret;
}
@@ -1217,6 +1224,9 @@ static struct clk gpmi_clk = {
};
static unsigned long saif_get_rate(struct clk *clk);
+static unsigned long saif_set_rate(struct clk *clk, unsigned int rate);
+static unsigned long saif_set_parent(struct clk *clk, struct clk *parent);
+
static struct clk saif_clk[] = {
{
.parent = &pll_clk[0],
@@ -1225,6 +1235,14 @@ static struct clk saif_clk[] = {
.disable = mx28_raw_disable,
.enable_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF0,
.enable_bits = BM_CLKCTRL_SAIF0_CLKGATE,
+ .scale_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF0,
+ .scale_bits = 0,
+ .busy_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF0,
+ .busy_bits = 29,
+ .bypass_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_CLKSEQ,
+ .bypass_bits = 0,
+ .set_rate = saif_set_rate,
+ .set_parent = saif_set_parent,
},
{
.parent = &pll_clk[0],
@@ -1233,6 +1251,14 @@ static struct clk saif_clk[] = {
.disable = mx28_raw_disable,
.enable_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF1,
.enable_bits = BM_CLKCTRL_SAIF1_CLKGATE,
+ .scale_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF1,
+ .scale_bits = 0,
+ .busy_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_SAIF1,
+ .busy_bits = 29,
+ .bypass_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_CLKSEQ,
+ .bypass_bits = 1,
+ .set_rate = saif_set_rate,
+ .set_parent = saif_set_parent,
},
};
@@ -1253,6 +1279,109 @@ static unsigned long saif_get_rate(struct clk *clk)
return (clk->parent->get_rate(clk->parent) / 0x10000) * div;
}
+static unsigned long saif_set_rate(struct clk *clk, unsigned int rate)
+{
+ u16 div = 0;
+ u32 clkctrl_saif;
+ u64 rates;
+ struct clk *parent = clk->parent;
+
+ pr_debug("%s: rate %d, parent rate %d\n", __func__, rate,
+ clk_get_rate(parent));
+
+ if (rate > clk_get_rate(parent))
+ return -EINVAL;
+ /*saif clock always use frac div*/
+ rates = 65536 * (u64)rate;
+ rates = rates + (u64)(clk_get_rate(parent) / 2);
+ do_div(rates, clk_get_rate(parent));
+ div = rates;
+
+ pr_debug("%s: div calculated is %d\n", __func__, div);
+ if (!div)
+ return -EINVAL;
+
+ clkctrl_saif = __raw_readl(clk->scale_reg);
+ clkctrl_saif &= ~BM_CLKCTRL_SAIF0_DIV_FRAC_EN;
+ clkctrl_saif &= ~BM_CLKCTRL_SAIF0_DIV;
+ clkctrl_saif |= div;
+ clkctrl_saif |= BM_CLKCTRL_SAIF0_DIV_FRAC_EN;
+ clkctrl_saif &= ~BM_CLKCTRL_SAIF0_CLKGATE;
+ __raw_writel(clkctrl_saif, clk->scale_reg);
+ if (clk->busy_reg) {
+ int i;
+ for (i = 10000; i; i--)
+ if (!clk_is_busy(clk))
+ break;
+ if (!i) {
+ pr_err("couldn't set up SAIF clk divisor\n");
+ return -ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+static unsigned long saif_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret = -EINVAL;
+ int shift = 4;
+ /*bypass*/
+ if (parent == &pll_clk[0])
+ shift = 8;
+ if (clk->bypass_reg) {
+ __raw_writel(1 << clk->bypass_bits, clk->bypass_reg + shift);
+ ret = 0;
+ }
+ return ret;
+}
+
+static int saif_mclk_enable(struct clk *clk)
+{
+ /*Check if enabled already*/
+ if (__raw_readl(clk->busy_reg) & clk->busy_bits)
+ return 0;
+ /*Enable saif to enable mclk*/
+ __raw_writel(0x1, clk->enable_reg);
+ mdelay(1);
+ __raw_writel(0x1, clk->enable_reg);
+ mdelay(1);
+ return 0;
+}
+
+static int saif_mclk_disable(struct clk *clk)
+{
+ /*Check if disabled already*/
+ if (!(__raw_readl(clk->busy_reg) & clk->busy_bits))
+ return 0;
+ /*Disable saif to disable mclk*/
+ __raw_writel(0x0, clk->enable_reg);
+ mdelay(1);
+ __raw_writel(0x0, clk->enable_reg);
+ mdelay(1);
+ return 0;
+}
+
+static struct clk saif_mclk[] = {
+ {
+ .parent = &saif_clk[0],
+ .enable = saif_mclk_enable,
+ .disable = saif_mclk_disable,
+ .enable_reg = SAIF0_CTRL,
+ .enable_bits = BM_SAIF_CTRL_RUN,
+ .busy_reg = SAIF0_STAT,
+ .busy_bits = BM_SAIF_STAT_BUSY,
+ },
+ {
+ .parent = &saif_clk[1],
+ .enable = saif_mclk_enable,
+ .disable = saif_mclk_disable,
+ .enable_reg = SAIF1_CTRL,
+ .enable_bits = BM_SAIF_CTRL_RUN,
+ .busy_reg = SAIF1_STAT,
+ .busy_bits = BM_SAIF_STAT_BUSY,
+ },
+};
+
static unsigned long pcmspdif_get_rate(struct clk *clk)
{
return clk->parent->get_rate(clk->parent) / 4;
@@ -1456,6 +1585,14 @@ static struct clk_lookup onchip_clocks[] = {
.con_id = "fec_clk",
.clk = &enet_out_clk,
},
+ {
+ .con_id = "saif_mclk.0",
+ .clk = &saif_mclk[0],
+ },
+ {
+ .con_id = "saif_mclk.1",
+ .clk = &saif_mclk[1],
+ }
};
static void mx28_clock_scan(void)
diff --git a/arch/arm/mach-mx28/device.c b/arch/arm/mach-mx28/device.c
index e1e447e77538..4e07d5261cd3 100644
--- a/arch/arm/mach-mx28/device.c
+++ b/arch/arm/mach-mx28/device.c
@@ -40,6 +40,7 @@
#include <mach/lcdif.h>
#include <mach/ddi_bc.h>
+#include "regs-digctl.h"
#include "device.h"
#include "mx28_pins.h"
@@ -1004,6 +1005,81 @@ static void __init mx28_init_dcp(void)
}
#endif
+#if defined(CONFIG_SND_MXS_SOC_DAI) || defined(CONFIG_SND_MXS_SOC_DAI_MODULE)
+static int audio_clk_init(void)
+{
+ struct clk *saif_clk;
+ struct clk *pll_clk;
+ int ret = -EINVAL;
+ saif_clk = clk_get(NULL, "saif.0");
+ if (IS_ERR(saif_clk)) {
+ pr_err("%s:failed to get saif_clk\n", __func__);
+ goto err_clk_init;
+ }
+ pll_clk = clk_get(NULL, "pll.0");
+ if (IS_ERR(pll_clk)) {
+ pr_err("%s:failed to get pll_clk\n", __func__);
+ goto err_clk_init;
+ }
+ ret = clk_set_parent(saif_clk, pll_clk);
+ if (ret) {
+ pr_err("%s:failed to set parent clk\n", __func__);
+ goto err_clk_init;
+ }
+ ret = 0;
+ /*set a default freq 12M to sgtl5000*/
+ clk_set_rate(saif_clk, 12000000);
+ clk_enable(saif_clk);
+ /*set the saif clk mux*/
+ __raw_writel(BF_DIGCTL_CTRL_SAIF_CLKMUX_SEL(0x0), \
+ IO_ADDRESS(DIGCTL_PHYS_ADDR) + HW_DIGCTL_CTRL);
+err_clk_init:
+ return ret;
+}
+
+static int audio_clk_finit(void)
+{
+ struct clk *saif_clk;
+ int ret = 0;
+ saif_clk = clk_get(NULL, "saif.0");
+ if (IS_ERR(saif_clk)) {
+ pr_err("%s:failed to get saif_clk\n", __func__);
+ ret = -EINVAL;
+ goto err_clk_finit;
+ }
+ clk_disable(saif_clk);
+err_clk_finit:
+ return ret;
+}
+
+static struct mxs_audio_platform_data audio_plat_data = {
+#if defined(CONFIG_SND_MXS_SOC_SAIF0_SELECT)
+ .saif0_select = 1,
+#endif
+#if defined(CONFIG_SND_MXS_SOC_SAIF1_SELECT)
+ .saif1_select = 1,
+#endif
+ .init = audio_clk_init,
+ .finit = audio_clk_finit,
+};
+#endif
+
+#if defined(CONFIG_SND_SOC_SGTL5000) || defined(CONFIG_SND_SOC_SGTL5000_MODULE)
+void __init mx28_init_audio(void)
+{ struct platform_device *pdev;
+ pdev = mxs_get_device("mxs-sgtl5000", 0);
+ if (pdev == NULL || IS_ERR(pdev))
+ return;
+ mxs_add_device(pdev, 3);
+ pdev->dev.platform_data = &audio_plat_data;
+}
+#else
+void __init mx28_init_audio(void)
+{
+}
+#endif
+
+
int __init mx28_device_init(void)
{
mx28_init_dma();
@@ -1018,6 +1094,7 @@ int __init mx28_device_init(void)
mx28_init_flexcan();
mx28_init_kbd();
mx28_init_ts();
+ mx28_init_audio();
mx28_init_lcdif();
mx28_init_pxp();
mx28_init_dcp();
diff --git a/arch/arm/mach-mx28/mx28evk.c b/arch/arm/mach-mx28/mx28evk.c
index a449571a134c..c3254e3c3c87 100644
--- a/arch/arm/mach-mx28/mx28evk.c
+++ b/arch/arm/mach-mx28/mx28evk.c
@@ -22,6 +22,7 @@
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
+#include <linux/i2c.h>
#include <asm/setup.h>
#include <asm/mach-types.h>
@@ -34,6 +35,16 @@
#include "device.h"
#include "mx28evk.h"
+static struct i2c_board_info __initdata mxs_i2c_device[] = {
+ { I2C_BOARD_INFO("sgtl5000-i2c", 0x14), .flags = I2C_M_TEN }
+};
+
+static void i2c_device_init(void)
+{
+ mxs_i2c_device[0].platform_data = (void *)clk_get(NULL, "saif_mclk.0");
+ i2c_register_board_info(0, mxs_i2c_device, ARRAY_SIZE(mxs_i2c_device));
+}
+
static void __init fixup_board(struct machine_desc *desc, struct tag *tags,
char **cmdline, struct meminfo *mi)
{
@@ -86,6 +97,7 @@ static void __init mx28evk_init_leds(void)
static void __init mx28evk_device_init(void)
{
/* Add mx28evk special code */
+ i2c_device_init();
mx28evk_init_leds();
}
diff --git a/arch/arm/mach-mx28/mx28evk_pins.c b/arch/arm/mach-mx28/mx28evk_pins.c
index faefa6177dc9..8bddbeb9f9e5 100644
--- a/arch/arm/mach-mx28/mx28evk_pins.c
+++ b/arch/arm/mach-mx28/mx28evk_pins.c
@@ -748,6 +748,49 @@ static struct pin_desc mx28evk_fixed_pins[] = {
.pull = 1,
},
#endif
+#if defined(CONFIG_SND_MXS_SOC_DAI) || defined(CONFIG_SND_MXS_SOC_DAI_MODULE)
+ /* Configurations of SAIF0 port pins */
+ {
+ .name = "SAIF0_MCLK",
+ .id = PINID_SAIF0_MCLK,
+ .fun = PIN_FUN1,
+ .strength = PAD_12MA,
+ .voltage = PAD_3_3V,
+ .pullup = 1,
+ .drive = 1,
+ .pull = 1,
+ },
+ {
+ .name = "SAIF0_LRCLK",
+ .id = PINID_SAIF0_LRCLK,
+ .fun = PIN_FUN1,
+ .strength = PAD_12MA,
+ .voltage = PAD_3_3V,
+ .pullup = 1,
+ .drive = 1,
+ .pull = 1,
+ },
+ {
+ .name = "SAIF0_BITCLK",
+ .id = PINID_SAIF0_BITCLK,
+ .fun = PIN_FUN1,
+ .strength = PAD_12MA,
+ .voltage = PAD_3_3V,
+ .pullup = 1,
+ .drive = 1,
+ .pull = 1,
+ },
+ {
+ .name = "SAIF0_SDATA0",
+ .id = PINID_SAIF0_SDATA0,
+ .fun = PIN_FUN1,
+ .strength = PAD_12MA,
+ .voltage = PAD_3_3V,
+ .pullup = 1,
+ .drive = 1,
+ .pull = 1,
+ },
+#endif
};
#if defined(CONFIG_FEC) || defined(CONFIG_FEC_MODULE)
diff --git a/arch/arm/plat-mxs/device.c b/arch/arm/plat-mxs/device.c
index f078414cd404..4cd1b0f0a47e 100644
--- a/arch/arm/plat-mxs/device.c
+++ b/arch/arm/plat-mxs/device.c
@@ -373,6 +373,19 @@ static struct platform_device mxs_battery = {
};
#endif
+#if defined(CONFIG_SND_SOC_SGTL5000) || \
+ defined(CONFIG_SND_SOC_SGTL5000_MODULE)
+static struct platform_device mxs_sgtl5000[] = {
+ {
+ .name = "mxs-sgtl5000",
+ .id = 0,
+ .dev = {
+ .release = mxs_nop_release,
+ },
+ },
+};
+#endif
+
static struct mxs_dev_lookup dev_lookup[] = {
#if defined(CONFIG_SERIAL_MXS_DUART) || \
defined(CONFIG_SERIAL_MXS_DUART_MODULE)
@@ -521,6 +534,14 @@ static struct mxs_dev_lookup dev_lookup[] = {
},
#endif
+#if defined(CONFIG_SND_SOC_SGTL5000) || \
+ defined(CONFIG_SND_SOC_SGTL5000_MODULE)
+ {
+ .name = "mxs-sgtl5000",
+ .size = ARRAY_SIZE(mxs_sgtl5000),
+ .pdev = mxs_sgtl5000,
+ },
+#endif
};
struct platform_device *mxs_get_device(char *name, int id)
diff --git a/arch/arm/plat-mxs/include/mach/device.h b/arch/arm/plat-mxs/include/mach/device.h
index fa4c312e98af..e6a5baab62c1 100644
--- a/arch/arm/plat-mxs/include/mach/device.h
+++ b/arch/arm/plat-mxs/include/mach/device.h
@@ -144,6 +144,24 @@ struct flexcan_platform_data {
unsigned int std_msg:1;
};
+struct mxs_audio_platform_data {
+ int saif0_select;
+ int saif1_select;
+ int intr_id_hp;
+ int ext_ram;
+ struct clk *saif_clock;
+
+ int hp_irq;
+ int (*hp_status) (void);
+
+ int sysclk;
+
+ int (*init) (void); /* board specific init */
+ int (*amp_enable) (int enable);
+ int (*finit) (void); /* board specific finit */
+ void *priv; /* used by board specific functions */
+};
+
extern void mxs_timer_init(struct mxs_sys_timer *timer);
extern void mxs_nomatch_timer_init(struct mxs_sys_timer *timer);
diff --git a/drivers/i2c/busses/i2c-mxs.c b/drivers/i2c/busses/i2c-mxs.c
index db20f6f3df25..705512b342e2 100644
--- a/drivers/i2c/busses/i2c-mxs.c
+++ b/drivers/i2c/busses/i2c-mxs.c
@@ -208,6 +208,7 @@ static void hw_i2c_dma_setup_write(u8 addr, void *buff, int len, int flags)
desc[2]->cmd.cmd.bits.command = DMA_READ;
desc[2]->cmd.address = i2c_buf_phys;
desc[2]->cmd.pio_words[0] = CMD_I2C_WRITE;
+ desc[2]->cmd.pio_words[0] |= BM_I2C_CTRL0_POST_SEND_STOP;
desc[2]->cmd.pio_words[0] |= BF_I2C_CTRL0_XFER_COUNT(len + 1) | flags;
i2c_buf_virt[0] = addr | I2C_WRITE;
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