aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r--sound/soc/samsung/Kconfig20
-rw-r--r--sound/soc/samsung/Makefile7
-rw-r--r--sound/soc/samsung/dma.c25
-rw-r--r--sound/soc/samsung/dma.h2
-rw-r--r--sound/soc/samsung/herring-wm8994.c358
-rw-r--r--sound/soc/samsung/i2s.c65
-rw-r--r--sound/soc/samsung/s3c-dma-wrapper.c267
-rw-r--r--sound/soc/samsung/s3c-dma.c478
-rw-r--r--sound/soc/samsung/s3c-dma.h33
-rw-r--r--sound/soc/samsung/s3c-idma.c533
-rw-r--r--sound/soc/samsung/s3c-idma.h37
-rw-r--r--sound/soc/samsung/s5p-i2s_sec.c355
-rw-r--r--sound/soc/samsung/s5pc1xx-i2s.c1152
-rw-r--r--sound/soc/samsung/s5pc1xx-i2s.h124
14 files changed, 3449 insertions, 7 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index d155cbb..2600074 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -20,6 +20,9 @@ config SND_S3C2412_SOC_I2S
select SND_S3C_I2SV2_SOC
select S3C2410_DMA
+config SND_S5PC1XX_I2S
+ tristate
+
config SND_SAMSUNG_PCM
tristate
@@ -34,6 +37,12 @@ config SND_SAMSUNG_SPDIF
config SND_SAMSUNG_I2S
tristate
+config S5P_INTERNAL_DMA
+ tristate
+
+config SND_S5P_WM8994_MASTER
+ bool
+
config SND_SOC_SAMSUNG_NEO1973_WM8753
tristate "Audio support for Openmoko Neo1973 Smartphones (GTA01/GTA02)"
depends on SND_SOC_SAMSUNG && (MACH_NEO1973_GTA01 || MACH_NEO1973_GTA02)
@@ -125,6 +134,17 @@ config SND_SOC_SAMSUNG_H1940_UDA1380
help
This driver provides audio support for HP iPAQ h1940 PDA.
+config SND_SOC_SAMSUNG_HERRING_WM8994
+ tristate "SoC I2S Audio support for HERRING - WM8994"
+ depends on SND_SOC_SAMSUNG && (MACH_HERRING || MACH_ARIES)
+ select SND_S5PC1XX_I2S
+ select SND_SOC_WM8994_SAMSUNG
+ select S5P_INTERNAL_DMA
+ select SND_S5P_WM8994_MASTER
+ help
+ Say Y if you want to add support for SoC audio on herring
+ with the WM8994.
+
config SND_SOC_SAMSUNG_RX1950_UDA1380
tristate "Audio support for the HP iPAQ RX1950"
depends on SND_SOC_SAMSUNG && MACH_RX1950
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
index 683843a..98a007e 100644
--- a/sound/soc/samsung/Makefile
+++ b/sound/soc/samsung/Makefile
@@ -1,7 +1,8 @@
# S3c24XX Platform Support
-snd-soc-s3c24xx-objs := dma.o
+snd-soc-s3c24xx-objs := dma.o s3c-dma-wrapper.o
snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
+snd-soc-s5pc1xx-i2s-objs := s5pc1xx-i2s.o
snd-soc-ac97-objs := ac97.o
snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
snd-soc-samsung-spdif-objs := spdif.o
@@ -13,9 +14,11 @@ obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
obj-$(CONFIG_SND_SAMSUNG_AC97) += snd-soc-ac97.o
obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
+obj-$(CONFIG_SND_S5PC1XX_I2S) += snd-soc-s5pc1xx-i2s.o
obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
+obj-$(CONFIG_S5P_INTERNAL_DMA) += s3c-idma.o s5p-i2s_sec.o
# S3C24XX Machine Support
snd-soc-jive-wm8750-objs := jive_wm8750.o
@@ -33,6 +36,7 @@ snd-soc-smdk-wm8994-objs := smdk_wm8994.o
snd-soc-smdk-wm9713-objs := smdk_wm9713.o
snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
snd-soc-goni-wm8994-objs := goni_wm8994.o
+snd-soc-herring-wm8994-objs := herring-wm8994.o
snd-soc-smdk-spdif-objs := smdk_spdif.o
snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
snd-soc-speyside-objs := speyside.o
@@ -46,6 +50,7 @@ obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_HERRING_WM8994) += snd-soc-herring-wm8994.o
obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o
diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c
index 5cb3b88..fc56ed0 100644
--- a/sound/soc/samsung/dma.c
+++ b/sound/soc/samsung/dma.c
@@ -42,9 +42,9 @@ static const struct snd_pcm_hardware dma_hardware = {
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
- .buffer_bytes_max = 128*1024,
- .period_bytes_min = PAGE_SIZE,
- .period_bytes_max = PAGE_SIZE*2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 128,
+ .period_bytes_max = 32 * 1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
@@ -191,6 +191,11 @@ static int dma_hw_params(struct snd_pcm_substream *substream,
prtd->dma_start = runtime->dma_addr;
prtd->dma_pos = prtd->dma_start;
prtd->dma_end = prtd->dma_start + totbytes;
+
+ pr_debug("DmaAddr=@%x Total=%lubytes PrdSz=%u #Prds=%u dma_area=0x%x\n",
+ prtd->dma_start, totbytes, params_period_bytes(params),
+ params_periods(params), (unsigned int)runtime->dma_area);
+
spin_unlock_irq(&prtd->lock);
return 0;
@@ -408,7 +413,11 @@ static void dma_free_dma_buffers(struct snd_pcm *pcm)
pr_debug("Entered %s\n", __func__);
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ for (stream = 1; stream < 2; stream++) {
+#else
for (stream = 0; stream < 2; stream++) {
+#endif
substream = pcm->streams[stream].substream;
if (!substream)
continue;
@@ -437,13 +446,14 @@ static int dma_new(struct snd_card *card,
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = 0xffffffff;
+#ifndef CONFIG_S5P_INTERNAL_DMA
if (dai->driver->playback.channels_min) {
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
-
+#endif
if (dai->driver->capture.channels_min) {
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
@@ -454,11 +464,14 @@ out:
return ret;
}
-static struct snd_soc_platform_driver samsung_asoc_platform = {
+struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
+EXPORT_SYMBOL_GPL(samsung_asoc_platform);
+
+#ifndef CONFIG_S5P_INTERNAL_DMA
static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
@@ -493,6 +506,8 @@ static void __exit samsung_asoc_exit(void)
}
module_exit(samsung_asoc_exit);
+#endif
+
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung ASoC DMA Driver");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h
index c506592..2bb0d44 100644
--- a/sound/soc/samsung/dma.h
+++ b/sound/soc/samsung/dma.h
@@ -19,4 +19,6 @@ struct s3c_dma_params {
int dma_size; /* Size of the DMA transfer */
};
+extern struct snd_soc_platform_driver samsung_asoc_platform;
+
#endif
diff --git a/sound/soc/samsung/herring-wm8994.c b/sound/soc/samsung/herring-wm8994.c
new file mode 100644
index 0000000..5f9dfe2
--- /dev/null
+++ b/sound/soc/samsung/herring-wm8994.c
@@ -0,0 +1,358 @@
+/*
+ * crespo_wm8994.c
+ *
+ * Copyright (C) 2010, Samsung Elect. Ltd. -
+ *
+ * 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.
+ */
+
+#include <linux/platform_device.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 <mach/regs-clock.h>
+#include <plat/regs-iis.h>
+#include "../codecs/wm8994.h"
+#include "s3c-dma.h"
+#include "s5pc1xx-i2s.h"
+//#include "s3c-i2s-v2.h"
+
+#include <linux/io.h>
+
+#define I2S_NUM 0
+#define SRC_CLK 66738000
+
+/* #define CONFIG_SND_DEBUG */
+#ifdef CONFIG_SND_DEBUG
+#define debug_msg(x...) printk(x)
+#else
+#define debug_msg(x...)
+#endif
+
+/* BLC(bits-per-channel) --> BFS(bit clock shud be >= FS*(Bit-per-channel)*2)*/
+/* BFS --> RFS(must be a multiple of BFS) */
+/* RFS & SRC_CLK --> Prescalar Value(SRC_CLK / RFS_VAL / fs - 1) */
+int smdkc110_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 *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int bfs, rfs, ret;
+ u32 ap_codec_clk;
+#ifndef CONFIG_SND_S5P_WM8994_MASTER
+ struct clk *clk_out, *clk_epll;
+ int psr;
+#endif
+ debug_msg("%s\n", __func__);
+
+ /* Choose BFS and RFS values combination that is supported by
+ * both the WM8994 codec as well as the S5P AP
+ *
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ /* Can take any RFS value for AP */
+ bfs = 16;
+ rfs = 256;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* Can take any RFS value for AP */
+ bfs = 32;
+ rfs = 256;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bfs = 48;
+ rfs = 512;
+ break;
+ /* Impossible, as the AP doesn't support 64fs or more BFS */
+ case SNDRV_PCM_FORMAT_S32_LE:
+ default:
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_SND_S5P_WM8994_MASTER
+ /* Set the Codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+
+ if (ret < 0) {
+ printk(KERN_ERR "smdkc110_wm8994_hw_params :\
+ Codec DAI configuration error!\n");
+ return ret;
+ }
+
+ /* Set the AP DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params :\
+ AP DAI configuration error!\n");
+ return ret;
+ }
+
+ /* Select the AP Sysclk */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CDCLKSRC_EXT,
+ params_rate(params), SND_SOC_CLOCK_IN);
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params :\
+ AP sys clock INT setting error!\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_I2SEXT,
+ params_rate(params), SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params :\
+ AP sys clock I2SEXT setting error!\n");
+ return ret;
+ }
+
+ switch (params_rate(params)) {
+
+ case 8000:
+ ap_codec_clk = 4096000;
+ break;
+ case 11025:
+ ap_codec_clk = 2822400;
+ break;
+ case 12000:
+ ap_codec_clk = 6144000;
+ break;
+ case 16000:
+ ap_codec_clk = 4096000;
+ break;
+ case 22050:
+ ap_codec_clk = 6144000;
+ break;
+ case 24000:
+ ap_codec_clk = 6144000;
+ break;
+ case 32000:
+ ap_codec_clk = 8192000;
+ break;
+ case 44100:
+ ap_codec_clk = 11289600;
+ break;
+ case 48000:
+ ap_codec_clk = 12288000;
+ break;
+ default:
+ ap_codec_clk = 11289600;
+ break;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL,
+ ap_codec_clk, 0);
+ if (ret < 0)
+ return ret;
+#else
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+
+ if (ret < 0)
+
+ return ret;
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_CDCLK,
+ params_rate(params), SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+#ifdef USE_CLKAUDIO
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_CLKSRC_CLKAUDIO,
+ params_rate(params), SND_SOC_CLOCK_OUT);
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params : \
+ AP sys clock setting error!\n");
+ return ret;
+ }
+#endif
+ clk_out = clk_get(NULL, "clk_out");
+ if (IS_ERR(clk_out)) {
+ printk(KERN_ERR
+ "failed to get CLK_OUT\n");
+ return -EBUSY;
+ }
+
+ clk_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(clk_epll)) {
+ printk(KERN_ERR
+ "failed to get fout_epll\n");
+ clk_put(clk_out);
+ return -EBUSY;
+ }
+
+ if (clk_set_parent(clk_out, clk_epll)) {
+ printk(KERN_ERR
+ "failed to set CLK_EPLL as parent of CLK_OUT\n");
+ clk_put(clk_out);
+ clk_put(clk_epll);
+ return -EBUSY;
+ }
+
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ clk_set_rate(clk_out, 12288000);
+ ap_codec_clk = SRC_CLK/4;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ default:
+ clk_set_rate(clk_out, 11289600);
+ ap_codec_clk = SRC_CLK/6;
+ break;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK,
+ ap_codec_clk, 0);
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params : \
+ Codec sys clock setting error!\n");
+ return ret;
+ }
+
+ /* Calculate Prescalare/PLL values for supported Rates */
+ psr = SRC_CLK / rfs / params_rate(params);
+ ret = SRC_CLK / rfs - psr * params_rate(params);
+ /* round off */
+ if (ret >= params_rate(params)/2)
+ psr += 1;
+
+ psr -= 1;
+ printk(KERN_INFO
+ "SRC_CLK=%d PSR=%d RFS=%d BFS=%d\n", SRC_CLK, psr, rfs, bfs);
+
+ /* Set the AP Prescalar/Pll */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_PRESCALER, psr);
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params :\
+ AP prescalar setting error!\n");
+ return ret;
+ }
+
+ /* Set the AP RFS */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, rfs);
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params : AP RFS setting error!\n");
+ return ret;
+ }
+
+ /* Set the AP BFS */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_BCLK, bfs);
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "smdkc110_wm8994_hw_params : AP BCLK setting error!\n");
+ return ret;
+ }
+
+ clk_put(clk_epll);
+ clk_put(clk_out);
+#endif
+ return 0;
+
+}
+
+/* machine stream operations */
+static struct snd_soc_ops smdkc110_ops = {
+ .hw_params = smdkc110_hw_params,
+};
+
+/* digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link smdkc1xx_dai = {
+ .name = "herring",
+ .stream_name = "WM8994 HiFi Playback",
+ .cpu_dai_name = "samsung-i2s.0",
+ .codec_dai_name = "WM8994 PAIFRX",
+ .platform_name = "samsung-audio",
+ .codec_name = "wm8994-samsung-codec.4-001a",
+ .ops = &smdkc110_ops,
+};
+
+static struct snd_soc_card smdkc100 = {
+ .name = "smdkc110",
+ .dai_link = &smdkc1xx_dai,
+ .num_links = 1,
+};
+
+#if 0
+static struct wm8994_setup_data smdkc110_wm8994_setup = {
+ /*
+ The I2C address of the WM89940 is 0x34. To the I2C driver
+ the address is a 7-bit number hence the right shift .
+ */
+ .i2c_address = 0x34,
+ .i2c_bus = 4,
+};
+
+/* audio subsystem */
+static struct snd_soc_device smdkc1xx_snd_devdata = {
+ .card = &smdkc100,
+ .codec_dev = &soc_codec_dev_wm8994,
+ .codec_data = &smdkc110_wm8994_setup,
+};
+#endif
+
+static struct platform_device *smdkc1xx_snd_device;
+static int __init smdkc110_audio_init(void)
+{
+ int ret;
+
+ debug_msg("%s\n", __func__);
+
+ smdkc1xx_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!smdkc1xx_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdkc1xx_snd_device, &smdkc100);
+ ret = platform_device_add(smdkc1xx_snd_device);
+
+ if (ret)
+ platform_device_put(smdkc1xx_snd_device);
+
+ return ret;
+}
+
+static void __exit smdkc110_audio_exit(void)
+{
+ debug_msg("%s\n", __func__);
+
+ platform_device_unregister(smdkc1xx_snd_device);
+}
+
+module_init(smdkc110_audio_init);
+module_exit(smdkc110_audio_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC SMDKC110 WM8994");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
index 992a732..a779185 100644
--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -14,6 +14,7 @@
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
+#include <linux/regulator/consumer.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
@@ -146,6 +147,8 @@ struct i2s_dai {
unsigned rfs, bfs;
/* I2S Controller's core clock */
struct clk *clk;
+ /* I2S Controller's power domain */
+ struct regulator *regulator;
/* Clock for generating I2S signals */
struct clk *op_clk;
/* Array of clock names for op_clk */
@@ -572,9 +575,13 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct i2s_dai *i2s = to_info(dai);
- u32 mod = readl(i2s->addr + I2SMOD);
+ u32 mod;
u32 tmp = 0;
+ dev_info(&i2s->pdev->dev, "base %p\n", i2s->addr);
+
+ mod = readl(i2s->addr + I2SMOD);
+
/* Format is priority */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_RIGHT_J:
@@ -951,6 +958,9 @@ static int i2s_resume(struct snd_soc_dai *dai)
static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
{
+ struct clk *fout_epll, *mout_epll;
+ struct clk *mout_audss = NULL;
+ struct clk *sclk_audio, *iis_clk, *iis_busclk, *iis_ipclk; /* these belong shomewhere else */
struct i2s_dai *i2s = to_info(dai);
struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
@@ -971,6 +981,59 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
}
clk_enable(i2s->clk);
+ /* Get i2s power domain regulator */
+ i2s->regulator = regulator_get(&i2s->pdev->dev, "pd");
+ if (IS_ERR(i2s->regulator)) {
+ dev_err(&i2s->pdev->dev, "%s: failed to get resource %s\n",
+ __func__, "i2s");
+ return PTR_ERR(i2s->regulator);
+ }
+
+ /* Enable Power domain */
+ regulator_enable(i2s->regulator);
+
+ fout_epll = clk_get(&i2s->pdev->dev, "fout_epll");
+ if (IS_ERR(fout_epll))
+ dev_err(&i2s->pdev->dev, "failed to get fout_epll\n");
+
+ mout_epll = clk_get(&i2s->pdev->dev, "mout_epll");
+ if (IS_ERR(mout_epll))
+ dev_err(&i2s->pdev->dev, "failed to get mout_epll\n");
+ clk_set_parent(mout_epll, fout_epll);
+
+ sclk_audio = clk_get(&i2s->pdev->dev, "sclk_audio");
+ if (IS_ERR(sclk_audio))
+ dev_err(&i2s->pdev->dev, "failed to get sclk_audio\n");
+ clk_set_parent(sclk_audio, mout_epll);
+
+ /* Need not to enable in general */
+ clk_enable(sclk_audio);
+
+ /* When I2S V5.1 used, initialize audio subsystem clock */
+ /* CLKMUX_ASS */
+ if (&i2s->pdev->id == 0) {
+ mout_audss = clk_get(NULL, "mout_audss");
+ if (IS_ERR(mout_audss))
+ dev_err(&i2s->pdev->dev, "failed to get mout_audss\n");
+ clk_set_parent(mout_audss, fout_epll);
+ /*MUX-I2SA*/
+ iis_clk = clk_get(&i2s->pdev->dev, "audio-bus");
+ if (IS_ERR(iis_clk))
+ dev_err(&i2s->pdev->dev, "failed to get audio-bus\n");
+ clk_set_parent(iis_clk, mout_audss);
+ /*getting AUDIO BUS CLK*/
+ iis_busclk = clk_get(NULL, "dout_audio_bus_clk_i2s");
+ if (IS_ERR(iis_busclk))
+ printk(KERN_ERR "failed to get audss_hclk\n");
+ iis_ipclk = clk_get(&i2s->pdev->dev, "i2s_v50");
+ if (IS_ERR(iis_ipclk))
+ dev_err(&i2s->pdev->dev, "failed to get i2s_v50_clock\n");
+ clk_enable(iis_ipclk);
+ clk_enable(iis_clk);
+ clk_enable(iis_busclk);
+ }
+
+
if (other) {
other->addr = i2s->addr;
other->clk = i2s->clk;
diff --git a/sound/soc/samsung/s3c-dma-wrapper.c b/sound/soc/samsung/s3c-dma-wrapper.c
new file mode 100644
index 0000000..2f87b68
--- /dev/null
+++ b/sound/soc/samsung/s3c-dma-wrapper.c
@@ -0,0 +1,267 @@
+/*
+ * s3c-dma-wrapper.c -- S3C DMA Platform Wrapper Driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * 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.
+ */
+
+#include <sound/soc.h>
+#include "dma.h"
+#include "s3c-idma.h"
+
+static int s3c_wrpdma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->hw_params)
+ return platform->ops->hw_params(substream, params);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->hw_free)
+ return platform->ops->hw_free(substream);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->prepare)
+ return platform->ops->prepare(substream);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->trigger)
+ return platform->ops->trigger(substream, cmd);
+ else
+ return 0;
+}
+
+static snd_pcm_uframes_t s3c_wrpdma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->pointer)
+ return platform->ops->pointer(substream);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->open)
+ return platform->ops->open(substream);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->close)
+ return platform->ops->close(substream);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_ioctl(struct snd_pcm_substream *substream,
+ unsigned int cmd, void *arg)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->ioctl)
+ return platform->ops->ioctl(substream, cmd, arg);
+ else
+ return 0;
+}
+
+static int s3c_wrpdma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_soc_platform_driver *platform;
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ platform = &idma_soc_platform;
+ else
+#endif
+ platform = &samsung_asoc_platform;
+
+ if (platform->ops->mmap)
+ return platform->ops->mmap(substream, vma);
+ else
+ return 0;
+}
+
+static struct snd_pcm_ops s3c_wrpdma_ops = {
+ .open = s3c_wrpdma_open,
+ .close = s3c_wrpdma_close,
+ .ioctl = s3c_wrpdma_ioctl,
+ .hw_params = s3c_wrpdma_hw_params,
+ .hw_free = s3c_wrpdma_hw_free,
+ .prepare = s3c_wrpdma_prepare,
+ .trigger = s3c_wrpdma_trigger,
+ .pointer = s3c_wrpdma_pointer,
+ .mmap = s3c_wrpdma_mmap,
+};
+
+static void s3c_wrpdma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_soc_platform_driver *gdma_platform;
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ struct snd_soc_platform_driver *idma_platform;
+#endif
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ idma_platform = &idma_soc_platform;
+ if (idma_platform->pcm_free)
+ idma_platform->pcm_free(pcm);
+#endif
+ gdma_platform = &samsung_asoc_platform;
+ if (gdma_platform->pcm_free)
+ gdma_platform->pcm_free(pcm);
+}
+
+static int s3c_wrpdma_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ struct snd_soc_platform_driver *gdma_platform;
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ struct snd_soc_platform_driver *idma_platform;
+#endif
+
+ /* sec_fifo i/f always use internal h/w buffers
+ * irrespective of the xfer method (iDMA or SysDMA) */
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ idma_platform = &idma_soc_platform;
+ if (idma_platform->pcm_new)
+ idma_platform->pcm_new(card, dai, pcm);
+#endif
+ gdma_platform = &samsung_asoc_platform;
+ if (gdma_platform->pcm_new)
+ gdma_platform->pcm_new(card, dai, pcm);
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver s3c_dma_wrapper = {
+ .ops = &s3c_wrpdma_ops,
+ .pcm_new = s3c_wrpdma_pcm_new,
+ .pcm_free = s3c_wrpdma_pcm_free,
+};
+//EXPORT_SYMBOL_GPL(s3c_dma_wrapper);
+
+static int __devinit s3c_dma_wrapper_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &s3c_dma_wrapper);
+}
+
+static int __devexit s3c_dma_wrapper_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver s3c_dma_wrapper_driver = {
+ .driver = {
+ .name = "samsung-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = s3c_dma_wrapper_platform_probe,
+ .remove = __devexit_p(s3c_dma_wrapper_platform_remove),
+};
+
+static int __init s3c_dma_wrapper_init(void)
+{
+ return platform_driver_register(&s3c_dma_wrapper_driver);
+}
+module_init(s3c_dma_wrapper_init);
+
+static void __exit s3c_dma_wrapper_exit(void)
+{
+ platform_driver_unregister(&s3c_dma_wrapper_driver);
+}
+module_exit(s3c_dma_wrapper_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("Audio DMA wrapper module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-dma.c b/sound/soc/samsung/s3c-dma.c
new file mode 100644
index 0000000..8985964
--- /dev/null
+++ b/sound/soc/samsung/s3c-dma.c
@@ -0,0 +1,478 @@
+/*
+ * s3c-dma.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+
+static const struct snd_pcm_hardware s3c_dma_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 128,
+ .period_bytes_max = 32 * 1024,
+ .periods_min = 2,
+ .periods_max = 128,
+ .fifo_size = 32,
+};
+
+struct s3c24xx_runtime_data {
+ spinlock_t lock;
+ int state;
+ unsigned int dma_loaded;
+ unsigned int dma_limit;
+ unsigned int dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+ struct s3c_dma_params *params;
+};
+
+/* s3c_dma_enqueue
+ *
+ * place a dma buffer onto the queue for the dma system
+ * to handle.
+*/
+static void s3c_dma_enqueue(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ dma_addr_t pos = prtd->dma_pos;
+ unsigned int limit;
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (s3c_dma_has_circular())
+ limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
+ else
+ limit = prtd->dma_limit;
+
+ pr_debug("%s: loaded %d, limit %d\n",
+ __func__, prtd->dma_loaded, limit);
+
+ while (prtd->dma_loaded < limit) {
+ unsigned long len = prtd->dma_period;
+
+ pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
+
+ if ((pos + len) > prtd->dma_end) {
+ len = prtd->dma_end - pos;
+ pr_debug(KERN_DEBUG "%s: corrected dma len %ld\n",
+ __func__, len);
+ }
+
+ ret = s3c2410_dma_enqueue(prtd->params->channel,
+ substream, pos, len);
+
+ if (ret == 0) {
+ prtd->dma_loaded++;
+ pos += prtd->dma_period;
+ if (pos >= prtd->dma_end)
+ pos = prtd->dma_start;
+ } else
+ break;
+ }
+
+ prtd->dma_pos = pos;
+}
+
+static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel,
+ void *dev_id, int size,
+ enum s3c2410_dma_buffresult result)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct s3c24xx_runtime_data *prtd;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
+ return;
+
+ prtd = substream->runtime->private_data;
+
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+
+ spin_lock(&prtd->lock);
+ if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) {
+ prtd->dma_loaded--;
+ s3c_dma_enqueue(substream);
+ }
+
+ spin_unlock(&prtd->lock);
+}
+
+static int s3c_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned long totbytes = params_buffer_bytes(params);
+ struct s3c_dma_params *dma =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ int ret = 0;
+
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* this may get called several times by oss emulation
+ * with different params -HW */
+ if (prtd->params == NULL) {
+ /* prepare DMA */
+ prtd->params = dma;
+
+ pr_debug("params %p, client %p, channel %d\n", prtd->params,
+ prtd->params->client, prtd->params->channel);
+
+ ret = s3c2410_dma_request(prtd->params->channel,
+ prtd->params->client, NULL);
+
+ if (ret < 0) {
+ printk(KERN_ERR "failed to get dma channel\n");
+ return ret;
+ }
+
+ /* use the circular buffering if we have it available. */
+ if (s3c_dma_has_circular())
+ s3c2410_dma_setflags(prtd->params->channel,
+ S3C2410_DMAF_CIRCULAR);
+ }
+
+ s3c2410_dma_set_buffdone_fn(prtd->params->channel,
+ s3c24xx_audio_buffdone);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totbytes;
+
+ spin_lock_irq(&prtd->lock);
+ prtd->dma_loaded = 0;
+ prtd->dma_limit = runtime->hw.periods_min;
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + totbytes;
+
+ pr_debug("DmaAddr=@%x Total=%lubytes PrdSz=%u #Prds=%u dma_area=0x%x\n",
+ prtd->dma_start, totbytes, params_period_bytes(params),
+ params_periods(params), (unsigned int)runtime->dma_area);
+
+ spin_unlock_irq(&prtd->lock);
+
+ return 0;
+}
+
+static int s3c_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* TODO - do we need to ensure DMA flushed */
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (prtd->params) {
+ s3c2410_dma_free(prtd->params->channel, prtd->params->client);
+ prtd->params = NULL;
+ }
+
+ return 0;
+}
+
+static int s3c_dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!prtd->params)
+ return 0;
+
+ /* channel needs configuring for mem=>device, increment memory addr,
+ * sync to pclk, half-word transfers to the IIS-FIFO. */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_MEM,
+ prtd->params->dma_addr);
+ } else {
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_HW,
+ prtd->params->dma_addr);
+ }
+
+ s3c2410_dma_config(prtd->params->channel,
+ prtd->params->dma_size);
+
+ /* flush the DMA channel */
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
+ prtd->dma_loaded = 0;
+ prtd->dma_pos = prtd->dma_start;
+
+ /* enqueue dma buffers */
+ s3c_dma_enqueue(substream);
+
+ return ret;
+}
+
+static int s3c_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->state |= ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->state &= ~ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+s3c_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+ unsigned long res;
+ dma_addr_t src, dst;
+
+ pr_debug("Entered %s\n", __func__);
+
+ spin_lock(&prtd->lock);
+ s3c2410_dma_getposition(prtd->params->channel, &src, &dst);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ res = dst - prtd->dma_start;
+ else
+ res = src - prtd->dma_start;
+
+ spin_unlock(&prtd->lock);
+
+ pr_debug("Pointer %x %x\n", src, dst);
+
+ /* we seem to be getting the odd error from the pcm library due
+ * to out-of-bounds pointers. this is maybe due to the dma engine
+ * not having loaded the new values for the channel before being
+ * callled... (todo - fix )
+ */
+
+ if (res >= snd_pcm_lib_buffer_bytes(substream)) {
+ if (res == snd_pcm_lib_buffer_bytes(substream))
+ res = 0;
+ }
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int s3c_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd;
+
+ pr_debug("Entered %s\n", __func__);
+
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ snd_soc_set_runtime_hwparams(substream, &s3c_dma_hardware);
+
+ prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+ return 0;
+}
+
+static int s3c_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (!prtd)
+ pr_debug("s3c_dma_close called with prtd == NULL\n");
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int s3c_dma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ pr_debug("Entered %s\n", __func__);
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops s3c_dma_ops = {
+ .open = s3c_dma_open,
+ .close = s3c_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = s3c_dma_hw_params,
+ .hw_free = s3c_dma_hw_free,
+ .prepare = s3c_dma_prepare,
+ .trigger = s3c_dma_trigger,
+ .pointer = s3c_dma_pointer,
+ .mmap = s3c_dma_mmap,
+};
+
+static int s3c_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = s3c_dma_hardware.buffer_bytes_max;
+
+ pr_debug("Entered %s\n", __func__);
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static void s3c_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ pr_debug("Entered %s\n", __func__);
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ for (stream = 1; stream < 2; stream++) {
+#else
+ for (stream = 0; stream < 2; stream++) {
+#endif
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 s3c_dma_mask = DMA_BIT_MASK(32);
+
+static int s3c_dma_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &s3c_dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+#ifndef CONFIG_S5P_INTERNAL_DMA
+ if (dai->driver->playback.channels_min) {
+ ret = s3c_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+#endif
+ if (dai->driver->capture.channels_min) {
+ ret = s3c_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform_driver s3c24xx_soc_platform = {
+ .ops = &s3c_dma_ops,
+ .pcm_new = s3c_dma_new,
+ .pcm_free = s3c_dma_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(s3c24xx_soc_platform);
+
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("Samsung S3C Audio DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-dma.h b/sound/soc/samsung/s3c-dma.h
new file mode 100644
index 0000000..cb869db
--- /dev/null
+++ b/sound/soc/samsung/s3c-dma.h
@@ -0,0 +1,33 @@
+/*
+ * s3c-dma.h --
+ *
+ * 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.
+ *
+ * ALSA PCM interface for the Samsung S3C24xx CPU
+ */
+
+#ifndef _S3C_AUDIO_H
+#define _S3C_AUDIO_H
+
+#define ST_RUNNING (1<<0)
+#define ST_OPENED (1<<1)
+
+struct s3c_dma_params {
+ struct s3c2410_dma_client *client; /* stream identifier */
+ int channel; /* Channel ID */
+ dma_addr_t dma_addr;
+ int dma_size; /* Size of the DMA transfer */
+};
+
+#define S3C24XX_DAI_I2S 0
+
+//#define pr_debug(fmt...) printk(fmt)
+/* platform data */
+extern struct snd_soc_platform_driver s3c24xx_soc_platform;
+extern struct snd_soc_platform s3c24xx_pcm_soc_platform;
+extern struct snd_ac97_bus_ops s3c24xx_ac97_ops;
+
+#endif
diff --git a/sound/soc/samsung/s3c-idma.c b/sound/soc/samsung/s3c-idma.c
new file mode 100644
index 0000000..c3b8c74
--- /dev/null
+++ b/sound/soc/samsung/s3c-idma.c
@@ -0,0 +1,533 @@
+/*
+ * s3c-idma.c -- I2S0's Internal Dma driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <plat/regs-iis.h>
+
+#include "s3c-dma.h"
+#include "s3c-idma.h"
+
+/** Debug **/
+#include <mach/map.h>
+#include <mach/regs-clock.h>
+#define dump_i2s() do { \
+ printk(KERN_INFO \
+ "%s:%s:%d\n", \
+ __FILE__, __func__, __LINE__); \
+ printk(KERN_INFO \
+ "\tS3C_IISCON : %x", \
+ readl(s3c_idma.regs + S3C2412_IISCON));\
+ printk(KERN_INFO \
+ "\tS3C_IISMOD : %x\n", \
+ readl(s3c_idma.regs + S3C2412_IISMOD));\
+ printk(KERN_INFO \
+ "\tS3C_IISFIC : %x", \
+ readl(s3c_idma.regs + S3C2412_IISFIC));\
+ printk(KERN_INFO \
+ "\tS3C_IISFICS : %x", \
+ readl(s3c_idma.regs + S5P_IISFICS));\
+ printk(KERN_INFO \
+ "\tS3C_IISPSR : %x\n", \
+ readl(s3c_idma.regs + S3C2412_IISPSR));\
+ printk(KERN_INFO \
+ "\tS3C_IISAHB : %x\n", \
+ readl(s3c_idma.regs + S5P_IISAHB));\
+ printk(KERN_INFO \
+ "\tS3C_IISSTR : %x\n", \
+ readl(s3c_idma.regs + S5P_IISSTR));\
+ printk(KERN_INFO \
+ "\tS3C_IISSIZE : %x\n", \
+ readl(s3c_idma.regs + S5P_IISSIZE));\
+ printk(KERN_INFO \
+ "\tS3C_IISADDR0 : %x\n", \
+ readl(s3c_idma.regs + S5P_IISADDR0));\
+ printk(KERN_INFO \
+ "\tS5P_CLKGATE_D20 : %x\n", \
+ readl(S5P_CLKGATE_D20));\
+ printk(KERN_INFO \
+ "\tS5P_LPMP_MODE_SEL : %x\n",\
+ readl(S5P_LPMP_MODE_SEL));\
+ } while (0)
+
+static const struct snd_pcm_hardware s3c_idma_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_U24_LE |
+ SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = MAX_LP_BUFF,
+ .period_bytes_min = 128,
+ .period_bytes_max = 16 * 1024,
+ .periods_min = 2,
+ .periods_max = 128,
+ .fifo_size = 64,
+};
+
+struct lpam_i2s_pdata {
+ spinlock_t lock;
+ int state;
+ dma_addr_t start;
+ dma_addr_t pos;
+ dma_addr_t end;
+ dma_addr_t period;
+
+};
+
+ /********************
+ * Internal DMA i/f *
+ ********************/
+static struct s3c_idma_info {
+ void __iomem *regs;
+ unsigned int dma_prd;
+ unsigned int dma_end;
+ spinlock_t lock;
+ void *token;
+ void (*cb)(void *dt, int bytes_xfer);
+} s3c_idma;
+
+
+static void s3c_idma_getpos(dma_addr_t *src)
+{
+ *src = LP_TXBUFF_ADDR +
+ (readl(s3c_idma.regs + S5P_IISTRNCNT) & 0xffffff) * 4;
+}
+
+void i2sdma_getpos(dma_addr_t *src)
+{
+ if (audio_clk_gated == 0 && i2s_trigger_stop == 0)
+ *src = LP_TXBUFF_ADDR +
+ (readl(s3c_idma.regs + S5P_IISTRNCNT) & 0xffffff) * 4;
+ else
+ *src = LP_TXBUFF_ADDR;
+
+}
+
+static int s3c_idma_enqueue(void *token)
+{
+ u32 val;
+
+ spin_lock(&s3c_idma.lock);
+ s3c_idma.token = token;
+ spin_unlock(&s3c_idma.lock);
+
+ pr_debug("%s: %x@%x\n", __func__, MAX_LP_BUFF, LP_TXBUFF_ADDR);
+
+ /* Internal DMA Level0 Interrupt Address */
+ val = LP_TXBUFF_ADDR + s3c_idma.dma_prd;
+ writel(val, s3c_idma.regs + S5P_IISADDR0);
+
+ /* Start address0 of I2S internal DMA operation. */
+ val = readl(s3c_idma.regs + S5P_IISSTR);
+ val = LP_TXBUFF_ADDR;
+ writel(val, s3c_idma.regs + S5P_IISSTR);
+
+ /*
+ * Transfer block size for I2S internal DMA.
+ * Should decide transfer size before start dma operation
+ */
+ val = readl(s3c_idma.regs + S5P_IISSIZE);
+ val &= ~(S5P_IISSIZE_TRNMSK << S5P_IISSIZE_SHIFT);
+
+ val |= ((((s3c_idma.dma_end & 0x1ffff) >> 2) &
+ S5P_IISSIZE_TRNMSK) << S5P_IISSIZE_SHIFT);
+ writel(val, s3c_idma.regs + S5P_IISSIZE);
+
+ return 0;
+}
+
+static void s3c_idma_setcallbk(void (*cb)(void *, int), unsigned prd)
+{
+ spin_lock(&s3c_idma.lock);
+ s3c_idma.cb = cb;
+ s3c_idma.dma_prd = prd;
+ spin_unlock(&s3c_idma.lock);
+
+ pr_debug("%s:%d dma_period=%x\n", __func__, __LINE__, s3c_idma.dma_prd);
+}
+
+static void s3c_idma_ctrl(int op)
+{
+ u32 val;
+
+ spin_lock(&s3c_idma.lock);
+
+ val = readl(s3c_idma.regs + S5P_IISAHB);
+
+ switch (op) {
+ case LPAM_DMA_START:
+ val |= (S5P_IISAHB_INTENLVL0 | S5P_IISAHB_DMAEN);
+ break;
+ case LPAM_DMA_STOP:
+ /* Disable LVL Interrupt and DMA Operation */
+ val &= ~(S5P_IISAHB_INTENLVL0 | S5P_IISAHB_DMAEN);
+ break;
+ default:
+ spin_unlock(&s3c_idma.lock);
+ return;
+ }
+
+ writel(val, s3c_idma.regs + S5P_IISAHB);
+
+ spin_unlock(&s3c_idma.lock);
+}
+
+static void s3c_idma_done(void *id, int bytes_xfer)
+{
+ struct snd_pcm_substream *substream = id;
+ struct lpam_i2s_pdata *prtd = substream->runtime->private_data;
+
+ pr_debug("%s:%d\n", __func__, __LINE__);
+
+ if (prtd && (prtd->state & ST_RUNNING))
+ snd_pcm_period_elapsed(substream);
+}
+
+static int s3c_idma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct lpam_i2s_pdata *prtd = substream->runtime->private_data;
+ unsigned long idma_totbytes;
+
+ pr_debug("Entered %s\n", __func__);
+
+ idma_totbytes = params_buffer_bytes(params);
+ prtd->end = LP_TXBUFF_ADDR + idma_totbytes;
+ prtd->period = params_periods(params);
+ s3c_idma.dma_end = prtd->end;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ memset(runtime->dma_area, 0, idma_totbytes);
+
+ runtime->dma_bytes = idma_totbytes;
+
+ s3c_idma_setcallbk(s3c_idma_done, params_period_bytes(params));
+
+ prtd->start = runtime->dma_addr;
+ prtd->pos = prtd->start;
+ prtd->end = prtd->start + idma_totbytes;
+
+ pr_debug("DmaAddr=@%x Total=%lubytes PrdSz=%u #Prds=%u dma_area=0x%x\n",
+ prtd->start, idma_totbytes, params_period_bytes(params),
+ prtd->period, (unsigned int)runtime->dma_area);
+
+ return 0;
+}
+
+static int s3c_idma_hw_free(struct snd_pcm_substream *substream)
+{
+ pr_debug("Entered %s\n", __func__);
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int s3c_idma_prepare(struct snd_pcm_substream *substream)
+{
+ struct lpam_i2s_pdata *prtd = substream->runtime->private_data;
+
+ pr_debug("Entered %s\n", __func__);
+
+ prtd->pos = prtd->start;
+
+ /* flush the DMA channel */
+ s3c_idma_ctrl(LPAM_DMA_STOP);
+ s3c_idma_enqueue((void *)substream);
+
+ return 0;
+}
+
+static int s3c_idma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct lpam_i2s_pdata *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->state |= ST_RUNNING;
+ s3c_idma_ctrl(LPAM_DMA_START);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->state &= ~ST_RUNNING;
+ s3c_idma_ctrl(LPAM_DMA_STOP);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+ s3c_idma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct lpam_i2s_pdata *prtd = runtime->private_data;
+ dma_addr_t src;
+ unsigned long res;
+
+ spin_lock(&prtd->lock);
+
+ s3c_idma_getpos(&src);
+ res = src - prtd->start;
+
+ spin_unlock(&prtd->lock);
+
+ if (res >= snd_pcm_lib_buffer_bytes(substream)) {
+ if (res == snd_pcm_lib_buffer_bytes(substream))
+ res = 0;
+ }
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int s3c_idma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long size, offset;
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ /* From snd_pcm_lib_mmap_iomem */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ vma->vm_flags |= VM_IO;
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ ret = io_remap_pfn_range(vma, vma->vm_start,
+ (runtime->dma_addr + offset) >> PAGE_SHIFT,
+ size, vma->vm_page_prot);
+
+ return ret;
+}
+
+static irqreturn_t s3c_iis_irq(int irqno, void *dev_id)
+{
+ u32 iiscon, iisahb, val, addr;
+
+ /* dump_i2s(); */
+ iisahb = readl(s3c_idma.regs + S5P_IISAHB);
+ iiscon = readl(s3c_idma.regs + S3C2412_IISCON);
+
+ if (iiscon & (1<<26)) {
+ pr_info("RxFIFO overflow interrupt\n");
+ writel(iiscon | (1<<26), s3c_idma.regs+S3C2412_IISCON);
+ }
+ if (iiscon & S5P_IISCON_FTXSURSTAT) {
+ iiscon |= S5P_IISCON_FTXURSTATUS;
+ writel(iiscon, s3c_idma.regs + S3C2412_IISCON);
+ pr_debug("TX_S underrun interrupt IISCON = 0x%08x\n",
+ readl(s3c_idma.regs + S3C2412_IISCON));
+ }
+
+ if (iiscon & S5P_IISCON_FTXURSTATUS) {
+ iiscon &= ~S5P_IISCON_FTXURINTEN;
+ iiscon |= S5P_IISCON_FTXURSTATUS;
+ writel(iiscon, s3c_idma.regs + S3C2412_IISCON);
+ pr_debug("TX_P underrun interrupt IISCON = 0x%08x\n",
+ readl(s3c_idma.regs + S3C2412_IISCON));
+ }
+
+ /* Check internal DMA level interrupt. */
+ if (iisahb & S5P_IISAHB_LVL0INT)
+ val = S5P_IISAHB_CLRLVL0;
+ else
+ val = 0;
+
+ if (val) {
+ iisahb |= val;
+ writel(iisahb, s3c_idma.regs + S5P_IISAHB);
+
+ addr = readl(s3c_idma.regs + S5P_IISADDR0);
+ addr += s3c_idma.dma_prd;
+
+ if (addr >= s3c_idma.dma_end)
+ addr = LP_TXBUFF_ADDR;
+
+ writel(addr, s3c_idma.regs + S5P_IISADDR0);
+
+ /* Finished dma transfer ? */
+ if (iisahb & S5P_IISLVLINTMASK) {
+ if (s3c_idma.cb)
+ s3c_idma.cb(s3c_idma.token, s3c_idma.dma_prd);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int s3c_idma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct lpam_i2s_pdata *prtd;
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ snd_soc_set_runtime_hwparams(substream, &s3c_idma_hardware);
+
+ prtd = kzalloc(sizeof(struct lpam_i2s_pdata), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ ret = request_irq(IRQ_I2S0, s3c_iis_irq, 0, "s3c-i2s", prtd);
+ if (ret < 0) {
+ pr_err("fail to claim i2s irq , ret = %d\n", ret);
+ kfree(prtd);
+ return ret;
+ }
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int s3c_idma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct lpam_i2s_pdata *prtd = runtime->private_data;
+
+ pr_debug("Entered %s, prtd = %p\n", __func__, prtd);
+
+ free_irq(IRQ_I2S0, prtd);
+
+ if (!prtd)
+ pr_err("s3c_idma_close called with prtd == NULL\n");
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static struct snd_pcm_ops s3c_idma_ops = {
+ .open = s3c_idma_open,
+ .close = s3c_idma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .trigger = s3c_idma_trigger,
+ .pointer = s3c_idma_pointer,
+ .mmap = s3c_idma_mmap,
+ .hw_params = s3c_idma_hw_params,
+ .hw_free = s3c_idma_hw_free,
+ .prepare = s3c_idma_prepare,
+};
+
+static void s3c_idma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+
+ pr_debug("Entered %s\n", __func__);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (!substream)
+ return;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ return;;
+
+ iounmap(buf->area);
+
+ buf->area = NULL;
+ buf->addr = 0;
+}
+
+static int s3c_idma_preallocate_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ pr_debug("Entered %s\n", __func__);
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ /* Assign PCM buffer pointers */
+ buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
+ buf->addr = LP_TXBUFF_ADDR;
+ buf->bytes = s3c_idma_hardware.buffer_bytes_max;
+ buf->area = (unsigned char *)ioremap(buf->addr, buf->bytes);
+ pr_info("%s: VA-%p PA-%X %ubytes\n",
+ __func__, buf->area, buf->addr, buf->bytes);
+
+ return 0;
+}
+
+static u64 s3c_idma_mask = DMA_BIT_MASK(32);
+
+static int s3c_idma_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &s3c_idma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->driver->playback.channels_min)
+ ret = s3c_idma_preallocate_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ return ret;
+}
+
+struct snd_soc_platform_driver idma_soc_platform = {
+ .ops = &s3c_idma_ops,
+ .pcm_new = s3c_idma_pcm_new,
+ .pcm_free = s3c_idma_pcm_free,
+};
+EXPORT_SYMBOL_GPL(idma_soc_platform);
+
+void s5p_idma_init(void *regs)
+{
+ spin_lock_init(&s3c_idma.lock);
+ s3c_idma.regs = regs;
+}
+
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("Samsung S5P LP-Audio DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-idma.h b/sound/soc/samsung/s3c-idma.h
new file mode 100644
index 0000000..351c3c3
--- /dev/null
+++ b/sound/soc/samsung/s3c-idma.h
@@ -0,0 +1,37 @@
+/*
+ * s3c-idma.h -- I2S0's Internal Dma driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __S3C_IDMA_H_
+#define __S3C_IDMA_H_
+
+#ifdef CONFIG_ARCH_S5PC1XX /* S5PC100 */
+#define MAX_LP_BUFF (128 * 1024)
+#define LP_DMA_PERIOD (105 * 1024)
+#else
+#define MAX_LP_BUFF (160 * 1024)
+#define LP_DMA_PERIOD (128 * 1024)
+#endif
+
+#define LP_TXBUFF_ADDR (0xC0000000)
+#define S5P_IISLVLINTMASK (0xf<<20)
+
+/* dma_state */
+#define LPAM_DMA_STOP 0
+#define LPAM_DMA_START 1
+
+extern struct snd_soc_platform_driver idma_soc_platform;
+extern int i2s_trigger_stop;
+extern bool audio_clk_gated ;
+void s5p_idma_init(void *regs);
+
+#endif /* __S3C_IDMA_H_ */
diff --git a/sound/soc/samsung/s5p-i2s_sec.c b/sound/soc/samsung/s5p-i2s_sec.c
new file mode 100644
index 0000000..747dd1c
--- /dev/null
+++ b/sound/soc/samsung/s5p-i2s_sec.c
@@ -0,0 +1,355 @@
+/*
+ * s5p-i2s_sec.c -- Secondary Fifo driver for I2S_v5
+ *
+ * (c) 2009 Samsung Electronics Co. Ltd
+ * - Jaswinder Singh Brar <jassi.brar@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/map.h>
+#include <mach/regs-clock.h>
+#include <plat/regs-iis.h>
+
+#include <mach/regs-audss.h>
+#include <mach/dma.h>
+#include "s3c-dma.h"
+#include "s3c-idma.h"
+#include "s3c-i2s-v2.h"
+
+#define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK
+#define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK
+#define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER
+
+#define S3C64XX_CLKSRC_PCLK (0)
+#define S3C64XX_CLKSRC_MUX (1)
+#define S3C64XX_CLKSRC_CDCLK (2)
+
+static void __iomem *s5p_i2s0_regs;
+
+static struct s3c2410_dma_client s5p_dma_client_outs = {
+ .name = "I2S_Sec PCM Stereo out"
+};
+
+static struct s3c_dma_params s5p_i2s_sec_pcm_out = {
+ .channel = DMACH_I2S0S_TX,
+ .client = &s5p_dma_client_outs,
+ .dma_size = 4,
+};
+
+static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+static void s5p_snd_rxctrl(int on)
+{
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(s5p_i2s0_regs + S3C2412_IISFIC);
+ con = readl(s5p_i2s0_regs + S3C2412_IISCON);
+ mod = readl(s5p_i2s0_regs + S3C2412_IISMOD);
+
+ pr_debug("%s: On=%d..IIS: CON=%x MOD=%x FIC=%x\n",
+ __func__, on, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ case S3C2412_IISMOD_MODE_RXONLY:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ printk(KERN_WARNING
+ "RXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(mod, s5p_i2s0_regs + S3C2412_IISMOD);
+ writel(con, s5p_i2s0_regs + S3C2412_IISCON);
+ } else {
+ /* See txctrl notes on FIFOs. */
+
+ con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+ con |= S3C2412_IISCON_RXDMA_PAUSE;
+ con |= S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_RXONLY:
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXONLY;
+ break;
+
+ default:
+ printk(KERN_WARNING
+ "RXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(con, s5p_i2s0_regs + S3C2412_IISCON);
+ writel(mod, s5p_i2s0_regs + S3C2412_IISMOD);
+ }
+
+ fic = readl(s5p_i2s0_regs + S3C2412_IISFIC);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+static void s5p_snd_txctrl(int on)
+{
+ u32 iiscon, iismod;
+ iiscon = readl(s5p_i2s0_regs + S3C2412_IISCON);
+ iismod = readl(s5p_i2s0_regs + S3C2412_IISMOD);
+ pr_debug("%s: On=%d . IIS: CON=%x MOD=%x\n",
+ __func__, on, iiscon, iismod);
+
+ if (on) {
+ iiscon |= S3C2412_IISCON_IIS_ACTIVE;
+ iiscon &= ~S3C2412_IISCON_TXCH_PAUSE;
+ iiscon &= ~S5P_IISCON_TXSDMAPAUSE;
+ iiscon |= S5P_IISCON_TXSDMACTIVE;
+
+ switch (iismod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXONLY:
+ case S3C2412_IISMOD_MODE_TXRX:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_RXONLY:
+ iismod &= ~S3C2412_IISMOD_MODE_MASK;
+ iismod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ printk(KERN_WARNING
+ "TXEN: Invalid MODE %x in IISMOD\n",
+ iismod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(iiscon, s5p_i2s0_regs + S3C2412_IISCON);
+ writel(iismod, s5p_i2s0_regs + S3C2412_IISMOD);
+ } else {
+ iiscon |= S5P_IISCON_TXSDMAPAUSE;
+ iiscon &= ~S5P_IISCON_TXSDMACTIVE;
+
+ /* return if primary is active */
+ if (iiscon & S3C2412_IISCON_TXDMA_ACTIVE) {
+ writel(iiscon, s5p_i2s0_regs + S3C2412_IISCON);
+ return;
+ }
+
+ iiscon |= S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (iismod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ iismod &= ~S3C2412_IISMOD_MODE_MASK;
+ iismod |= S3C2412_IISMOD_MODE_RXONLY;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ iismod &= ~S3C2412_IISMOD_MODE_MASK;
+ iiscon &= ~S3C2412_IISCON_IIS_ACTIVE;
+ break;
+
+ default:
+ printk(KERN_WARNING
+ "TXDIS: Invalid MODE %x in IISMOD\n",
+ iismod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+
+ writel(iismod, s5p_i2s0_regs + S3C2412_IISMOD);
+ writel(iiscon, s5p_i2s0_regs + S3C2412_IISCON);
+ }
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s5p_snd_lrsync(void)
+{
+ u32 iiscon;
+ unsigned long loops = msecs_to_loops(1);
+
+ pr_debug("Entered %s\n", __func__);
+
+ while (--loops) {
+ iiscon = readl(s5p_i2s0_regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_LRINDEX)
+ break;
+
+ cpu_relax();
+ }
+
+ if (!loops) {
+ pr_debug("%s: timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+int s5p_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ u32 iismod;
+
+ snd_soc_dai_set_dma_data(rtd->cpu_dai,
+ substream, &s5p_i2s_sec_pcm_out);
+
+ iismod = readl(s5p_i2s0_regs + S3C2412_IISMOD);
+
+ /* Copy the same bps as Primary */
+ iismod &= ~S5P_IISMOD_BLCSMASK;
+ iismod |= ((iismod & S5P_IISMOD_BLCPMASK) << 2);
+
+ writel(iismod, s5p_i2s0_regs + S3C2412_IISMOD);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_i2s_hw_params);
+
+int s5p_i2s_startup(struct snd_soc_dai *dai)
+{
+ u32 iiscon, iisfic;
+ u32 iismod, iisahb;
+
+ iiscon = readl(s5p_i2s0_regs + S3C2412_IISCON);
+ iismod = readl(s5p_i2s0_regs + S3C2412_IISMOD);
+ iisahb = readl(s5p_i2s0_regs + S5P_IISAHB);
+
+ iisahb |= (S5P_IISAHB_DMARLD | S5P_IISAHB_DISRLDINT);
+ iismod |= S5P_IISMOD_TXSLP;
+
+ writel(iisahb, s5p_i2s0_regs + S5P_IISAHB);
+ writel(iismod, s5p_i2s0_regs + S3C2412_IISMOD);
+
+ s5p_snd_txctrl(0);
+ /*
+ * Don't turn-off RX setting as recording may be
+ * active during playback startup
+ * s5p_snd_rxctrl(0);
+ */
+
+ /* FIFOs must be flushed before enabling PSR
+ * and other MOD bits, so we do it here. */
+ if (iiscon & S5P_IISCON_TXSDMACTIVE)
+ return 0;
+
+ iisfic = readl(s5p_i2s0_regs + S5P_IISFICS);
+ iisfic |= S3C2412_IISFIC_TXFLUSH;
+ writel(iisfic, s5p_i2s0_regs + S5P_IISFICS);
+
+ do {
+ cpu_relax();
+ } while ((__raw_readl(s5p_i2s0_regs + S5P_IISFICS) >> 8) & 0x7f);
+
+ iisfic = readl(s5p_i2s0_regs + S5P_IISFICS);
+ iisfic &= ~S3C2412_IISFIC_TXFLUSH;
+ writel(iisfic, s5p_i2s0_regs + S5P_IISFICS);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_i2s_startup);
+
+int i2s_trigger_stop ;
+EXPORT_SYMBOL_GPL(i2s_trigger_stop);
+int s5p_i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /* We don't configure clocks from this Sec i/f.
+ * So, we simply wait enough time for LRSYNC to
+ * get synced and not check return 'error'
+ */
+ s5p_snd_lrsync();
+ s5p_snd_txctrl(1);
+ i2s_trigger_stop = 0;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ i2s_trigger_stop = 1;
+ s5p_snd_txctrl(0);
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_i2s_trigger);
+
+struct snd_soc_dai i2s_sec_fifo_dai = {
+ .name = "i2s-sec-fifo",
+ .id = 0,
+};
+EXPORT_SYMBOL_GPL(i2s_sec_fifo_dai);
+
+
+void s5p_i2s_sec_init(void *regs, dma_addr_t phys_base)
+{
+ u32 val;
+#ifdef CONFIG_ARCH_S5PV210
+ /* We use I2SCLK for rate generation, so set EPLLout as
+ * the parent of I2SCLK.
+ */
+ val = readl(S5P_CLKSRC_AUDSS);
+ val &= ~(0x3<<2);
+ val |= (1<<0);
+ writel(val, S5P_CLKSRC_AUDSS);
+
+ val = readl(S5P_CLKGATE_AUDSS);
+ val |= (0x7f<<0);
+ writel(val, S5P_CLKGATE_AUDSS);
+#else
+ #error INITIALIZE HERE!
+#endif
+
+ s5p_i2s0_regs = regs;
+ s5p_i2s_sec_pcm_out.dma_addr = phys_base + S5P_IISTXDS;
+
+ s5p_snd_rxctrl(0);
+ s5p_snd_txctrl(0);
+ s5p_idma_init(regs);
+}
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("S5P I2S-SecFifo SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s5pc1xx-i2s.c b/sound/soc/samsung/s5pc1xx-i2s.c
new file mode 100644
index 0000000..674ccaa
--- /dev/null
+++ b/sound/soc/samsung/s5pc1xx-i2s.c
@@ -0,0 +1,1152 @@
+/* sound/soc/s3c24xx/s5pc1xx-i2s.c
+ *
+ * ALSA SoC Audio Layer - S3C64XX I2S driver
+ *
+ * Copyright 2008 Openmoko, Inc.
+ * Copyright 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/regulator/consumer.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <plat/regs-iis.h>
+#include <plat/audio.h>
+
+#include <mach/dma.h>
+
+#include <mach/map.h>
+#include <mach/regs-audss.h>
+#include <mach/regs-clock.h>
+#include <linux/wakelock.h>
+#include "s3c-dma.h"
+#include "s5pc1xx-i2s.h"
+
+/*
+ * The value should be set to maximum of the total number
+ * of I2Sv3 controllers that any supported SoC has.
+ */
+#define MAX_I2SV3 2
+
+static struct s3c2410_dma_client s3c64xx_dma_client_out = {
+ .name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c64xx_dma_client_in = {
+ .name = "I2S PCM Stereo in"
+};
+
+static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops;
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[MAX_I2SV3];
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[MAX_I2SV3];
+static struct s3c_i2sv2_info s3c64xx_i2s[MAX_I2SV3];
+
+static struct snd_soc_dai_driver s3c64xx_i2s_dai_driver[MAX_I2SV3];
+bool audio_clk_gated; /* At first, clock & i2s0_pd is enabled in probe() */
+//EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai);
+
+/* For I2S Clock/Power Gating */
+static int tx_clk_enabled ;
+static int rx_clk_enabled ;
+static int reg_saved_ok ;
+
+void dump_i2s(struct s3c_i2sv2_info *i2s)
+{
+ printk(KERN_INFO "IISMOD=0x%x..IISCON=0x%x..IISPSR=0x%x\
+ IISAHB=0x%x..\n" , readl(i2s->regs + S3C2412_IISMOD),
+ readl(i2s->regs + S3C2412_IISCON),
+ readl(i2s->regs + S3C2412_IISPSR),
+ readl(i2s->regs + S5P_IISAHB));
+ printk(KERN_INFO "..AUDSSRC=0x%x..AUDSSDIV=0x%x..\
+ AUDSSGATE=0x%x..\n" , readl(S5P_CLKSRC_AUDSS),
+ readl(S5P_CLKDIV_AUDSS), readl(S5P_CLKGATE_AUDSS));
+}
+
+#define dump_reg(iis)
+
+static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C64XX_IISMOD_IMS_SYSMUX)
+ return i2s->iis_clk;
+ else
+ return i2s->iis_ipclk;
+}
+EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock);
+
+void s5p_i2s_set_clk_enabled(struct snd_soc_dai *dai, bool state)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+
+ pr_debug("..entering %s\n", __func__);
+
+ if (state) {
+ if (audio_clk_gated == 1)
+ regulator_enable(i2s->regulator);
+
+ if (dai->id == 0) { /* I2S V5.1? */
+ clk_enable(i2s->iis_ipclk);
+ clk_enable(i2s->iis_clk);
+ clk_enable(i2s->iis_busclk);
+ }
+ audio_clk_gated = 0;
+ } else {
+ if (dai->id == 0) { /* I2S V5.1? */
+ clk_disable(i2s->iis_busclk);
+ clk_disable(i2s->iis_clk);
+ clk_disable(i2s->iis_ipclk);
+ }
+
+ if (audio_clk_gated == 0)
+ regulator_disable(i2s->regulator);
+
+ audio_clk_gated = 1;
+ }
+}
+
+static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_set_dma_data(dai, substream,
+ i2s->dma_playback);
+ else
+ snd_soc_dai_set_dma_data(dai, substream,
+ i2s->dma_capture);
+
+ /* Working copies of register */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ switch (params_channels(params)) {
+ case 1:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s->dma_playback->dma_size = 2;
+ else
+ i2s->dma_capture->dma_size = 2;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ iismod |= S3C2412_IISMOD_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iismod &= ~S3C2412_IISMOD_8BIT;
+ break;
+ }
+#endif
+
+#if defined(CONFIG_PLAT_S3C64XX) || defined(CONFIG_PLAT_S5P)
+ iismod &= ~(S3C64XX_IISMOD_BLC_MASK | S3C2412_IISMOD_BCLK_MASK);
+ /* Sample size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ /* 8 bit sample, 16fs BCLK */
+ iismod |= (S3C64XX_IISMOD_BLC_8BIT | S3C2412_IISMOD_BCLK_16FS);
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* 16 bit sample, 32fs BCLK */
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ /* 24 bit sample, 48fs BCLK */
+ iismod |= (S3C64XX_IISMOD_BLC_24BIT | S3C2412_IISMOD_BCLK_48FS);
+ break;
+ }
+
+ /* Set the IISMOD[25:24](BLC_P) to same value */
+ iismod &= ~(S5P_IISMOD_BLCPMASK);
+ iismod |= ((iismod & S3C64XX_IISMOD_BLC_MASK) << 11);
+#endif
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+ return 0;
+}
+
+static int s5p_i2s_wr_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ s5p_i2s_hw_params(substream, params, dai);
+ else
+ s3c2412_i2s_hw_params(substream, params, dai);
+
+#else
+ s3c2412_i2s_hw_params(substream, params, dai);
+#endif
+ return 0;
+}
+
+#define S3C2412_I2S_DEBUG_CON 1
+
+#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
+
+#if S3C2412_I2S_DEBUG_CON
+static void dbg_showcon(const char *fn, u32 con)
+{
+ printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d,\
+ RXFFULL=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_LRINDEX),
+ bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
+ bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
+
+ printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
+ fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
+ printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
+}
+#else
+static inline void dbg_showcon(const char *fn, u32 con)
+{
+}
+#endif
+
+/* Turn on or off the transmission path. */
+static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_TXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXONLY:
+ case S3C2412_IISMOD_MODE_TXRX:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_RXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ } else {
+ /* Note, we do not have any indication that the FIFO problems
+ * tha the S3C2410/2440 had apply here, so we should be able
+ * to disable the DMA and TX without resetting the FIFOS.
+ */
+
+ con |= S3C2412_IISCON_TXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
+ if (con & S5P_IISCON_TXSDMACTIVE) { /* If sec is active */
+ writel(con, regs + S3C2412_IISCON);
+ return;
+ }
+ con |= S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_RXONLY;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ dbg_showcon(__func__, con);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ case S3C2412_IISMOD_MODE_RXONLY:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ } else {
+ /* See txctrl notes on FIFOs. */
+
+ con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+ con |= S3C2412_IISCON_RXDMA_PAUSE;
+ con |= S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_RXONLY:
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXONLY;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s)
+{
+ u32 iiscon;
+ unsigned long loops = msecs_to_loops(5);
+
+ pr_debug("Entered %s\n", __func__);
+
+ while (--loops) {
+ iiscon = readl(i2s->regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_LRINDEX)
+ break;
+
+ cpu_relax();
+ }
+
+ if (!loops) {
+ printk(KERN_ERR "%s: timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ unsigned long irqs;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!i2s->master) {
+ ret = s3c2412_snd_lrsync(i2s);
+ if (ret)
+ goto exit_err;
+ }
+
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 1);
+ else
+ s3c2412_snd_txctrl(i2s, 1);
+
+ local_irq_restore(irqs);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 0);
+ else
+ s3c2412_snd_txctrl(i2s, 0);
+
+ local_irq_restore(irqs);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+static int s5p_i2s_wr_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ s5p_i2s_trigger(substream, cmd, dai);
+ else
+ s3c2412_i2s_trigger(substream, cmd, dai);
+
+#else
+ s3c2412_i2s_trigger(substream, cmd, dai);
+#endif
+ return 0;
+}
+
+/*
+ * Set S3C2412 I2S DAI format
+ */
+static int s5p_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x\n", iismod);
+
+#if defined(CONFIG_PLAT_S3C64XX) || defined(CONFIG_PLAT_S5P)
+ /* From Rev1.1 datasheet, we have two master and two slave modes:
+ * IMS[11:10]:
+ * 00 = master mode, fed from PCLK
+ * 01 = master mode, fed from CLKAUDIO
+ * 10 = slave mode, using PCLK
+ * 11 = slave mode, using I2SCLK
+ */
+#define IISMOD_MASTER_MASK (1 << 11)
+#define IISMOD_SLAVE (1 << 11)
+#define IISMOD_MASTER (0 << 11)
+#endif
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2s->master = 0;
+ iismod &= ~IISMOD_MASTER_MASK;
+ iismod |= IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->master = 1;
+ iismod &= ~IISMOD_MASTER_MASK;
+ iismod |= IISMOD_MASTER;
+ break;
+ default:
+ pr_err("unknwon master/slave format\n");
+ return -EINVAL;
+ }
+
+ iismod &= ~S3C2412_IISMOD_SDF_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_MSB;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_LSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod &= ~S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_IIS;
+ break;
+ default:
+ pr_err("Unknown data format\n");
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x\n", iismod);
+ return 0;
+}
+
+static int s5p_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 reg;
+
+ pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
+
+ switch (div_id) {
+ case S3C_I2SV2_DIV_BCLK:
+ if (div > 3) {
+ /* convert value to bit field */
+ switch (div) {
+ case 16:
+ div = S3C2412_IISMOD_BCLK_16FS;
+ break;
+ case 32:
+ div = S3C2412_IISMOD_BCLK_32FS;
+ break;
+ case 24:
+ div = S3C2412_IISMOD_BCLK_24FS;
+ break;
+ case 48:
+ div = S3C2412_IISMOD_BCLK_48FS;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_BCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: MOD=%08x\n", __func__,
+ readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_RCLK:
+ if (div > 3) {
+ /* convert value to bit field */
+
+ switch (div) {
+ case 256:
+ div = S3C2412_IISMOD_RCLK_256FS;
+ break;
+ case 384:
+ div = S3C2412_IISMOD_RCLK_384FS;
+ break;
+ case 512:
+ div = S3C2412_IISMOD_RCLK_512FS;
+ break;
+ case 768:
+ div = S3C2412_IISMOD_RCLK_768FS;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_RCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: MOD=%08x\n", __func__,
+ readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_PRESCALER:
+ if (div >= 0)
+ writel((div << 8) | S3C2412_IISPSR_PSREN,
+ i2s->regs + S3C2412_IISPSR);
+ else
+ writel(0x0, i2s->regs + S3C2412_IISPSR);
+ pr_debug("%s: PSR=%08x\n", __func__,
+ readl(i2s->regs + S3C2412_IISPSR));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s5p_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct clk *clk;
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ switch (clk_id) {
+ case S3C64XX_CLKSRC_PCLK:
+ iismod &= ~S3C64XX_IISMOD_IMS_SYSMUX;
+ break;
+ case S3C64XX_CLKSRC_MUX:
+ iismod |= S3C64XX_IISMOD_IMS_SYSMUX;
+ break;
+
+ case S3C64XX_CLKSRC_CDCLK:
+ switch (dir) {
+ case SND_SOC_CLOCK_IN:
+ iismod |= S3C64XX_IISMOD_CDCLKCON;
+ break;
+ case SND_SOC_CLOCK_OUT:
+ iismod &= ~S3C64XX_IISMOD_CDCLKCON;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+#ifdef USE_CLKAUDIO
+ /* IIS-IP is Master and derives its clocks from I2SCLKD2 */
+ case S3C_CLKSRC_CLKAUDIO:
+ if (!i2s->master)
+ return -EINVAL;
+ iismod &= ~S3C_IISMOD_IMSMASK;
+ iismod |= clk_id;
+ clk = clk_get(NULL, "fout_epll");
+ if (IS_ERR(clk)) {
+ printk(KERN_ERR
+ "failed to get %s\n", "fout_epll");
+ return -EBUSY;
+ }
+ clk_disable(clk);
+ switch (freq) {
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ clk_set_rate(clk, 49152000);
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ default:
+ clk_set_rate(clk, 67738000);
+ break;
+ }
+ clk_enable(clk);
+ clk_put(clk);
+ break;
+#endif
+ /* IIS-IP is Slave and derives its clocks from the Codec Chip */
+ case S3C64XX_CLKSRC_I2SEXT:
+ iismod &= ~S3C64XX_IISMOD_IMSMASK;
+ iismod |= clk_id;
+ /* Operation clock for I2S logic selected as Audio Bus Clock */
+ iismod |= S3C64XX_IISMOD_OPPCLK;
+
+ clk = clk_get(NULL, "fout_epll");
+ if (IS_ERR(clk)) {
+ printk(KERN_ERR
+ "failed to get %s\n", "fout_epll");
+ return -EBUSY;
+ }
+ clk_disable(clk);
+ clk_set_rate(clk, 67738000);
+ clk_enable(clk);
+ clk_put(clk);
+ break;
+
+ case S3C64XX_CDCLKSRC_EXT:
+ iismod |= S3C64XX_IISMOD_CDCLKCON;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+
+ return 0;
+}
+
+static int s5p_i2s_wr_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 iiscon, iisfic;
+
+ if (!tx_clk_enabled && !rx_clk_enabled) {
+ s5p_i2s_set_clk_enabled(dai, 1);
+ if (reg_saved_ok == true) {
+ /* Is this dai for I2Sv5? (I2S0) */
+ if (dai->id == 0) {
+ writel(i2s->suspend_audss_clksrc,
+ S5P_CLKSRC_AUDSS);
+ writel(i2s->suspend_audss_clkdiv,
+ S5P_CLKDIV_AUDSS);
+ writel(i2s->suspend_audss_clkgate,
+ S5P_CLKGATE_AUDSS);
+ }
+ writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+ writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+ writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+ writel(i2s->suspend_iisahb, i2s->regs + S5P_IISAHB);
+ reg_saved_ok = false;
+ pr_debug("I2S Audio Clock enabled and \
+ Registers restored...\n");
+ }
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_debug("Inside..%s..for playback stream\n" , __func__);
+ tx_clk_enabled = 1;
+ } else {
+ pr_debug("Inside..%s..for capture stream\n" , __func__);
+ rx_clk_enabled = 1;
+ iiscon = readl(i2s->regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_RXDMA_ACTIVE)
+ return 0;
+
+ iisfic = readl(i2s->regs + S3C2412_IISFIC);
+ iisfic |= S3C2412_IISFIC_RXFLUSH;
+ writel(iisfic, i2s->regs + S3C2412_IISFIC);
+
+ do {
+ cpu_relax();
+ } while ((__raw_readl(i2s->regs + S3C2412_IISFIC) >> 0) & 0x7f);
+
+ iisfic = readl(i2s->regs + S3C2412_IISFIC);
+ iisfic &= ~S3C2412_IISFIC_RXFLUSH;
+ writel(iisfic, i2s->regs + S3C2412_IISFIC);
+ }
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ s5p_i2s_startup(dai);
+#endif
+ dump_reg(i2s);
+ return 0;
+}
+
+static void s5p_i2s_wr_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_debug("Inside %s for playback stream\n" , __func__);
+ tx_clk_enabled = 0;
+ } else {
+ pr_debug("Inside..%s..for capture stream\n" , __func__);
+ if (readl(i2s->regs + S3C2412_IISCON) & (1<<26)) {
+ pr_debug("\n rx overflow int in %s\n" , __func__);
+ /* clear rxfifo overflow interrupt */
+ writel(readl(i2s->regs + S3C2412_IISCON) | (1<<26),
+ i2s->regs + S3C2412_IISCON);
+ /* flush rx */
+ writel(readl(i2s->regs + S3C2412_IISFIC) | (1<<7) ,
+ i2s->regs + S3C2412_IISFIC);
+ }
+ rx_clk_enabled = 0;
+ }
+
+ if (!tx_clk_enabled && !rx_clk_enabled) {
+ i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+ i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+ i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+ i2s->suspend_iisahb = readl(i2s->regs + S5P_IISAHB);
+ /* Is this dai for I2Sv5? (I2S0) */
+ if (dai->id == 0) {
+ i2s->suspend_audss_clksrc = readl(S5P_CLKSRC_AUDSS);
+ i2s->suspend_audss_clkdiv = readl(S5P_CLKDIV_AUDSS);
+ i2s->suspend_audss_clkgate = readl(S5P_CLKGATE_AUDSS);
+ }
+ reg_saved_ok = true;
+ s5p_i2s_set_clk_enabled(dai, 0);
+ pr_debug("I2S Audio Clock disabled and Registers stored...\n");
+ pr_debug("Inside %s CLkGATE_IP3=0x%x..\n",
+ __func__ , __raw_readl(S5P_CLKGATE_IP3));
+ }
+
+ return;
+}
+
+#define S3C64XX_I2S_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_KNOT)
+
+#define S3C64XX_I2S_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static void s3c64xx_iis_dai_init(struct snd_soc_dai_driver *dai)
+{
+ dai->name = "s5pc1xx-i2s";
+ dai->playback.channels_min = 2;
+ dai->playback.channels_max = 2;
+ dai->playback.rates = S3C64XX_I2S_RATES;
+ dai->playback.formats = S3C64XX_I2S_FMTS;
+ dai->capture.channels_min = 1;
+ dai->capture.channels_max = 2;
+ dai->capture.rates = S3C64XX_I2S_RATES;
+ dai->capture.formats = S3C64XX_I2S_FMTS;
+ dai->ops = &s3c64xx_i2s_dai_ops;
+}
+
+/* suspend/resume are not necessary due to Clock/Pwer gating scheme... */
+#ifdef CONFIG_PM
+static int s5p_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+
+ if (reg_saved_ok != true) {
+ dump_reg(i2s);
+ i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+ i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+ i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+ i2s->suspend_iisahb = readl(i2s->regs + S5P_IISAHB);
+ if (dai->id == 0) {
+ i2s->suspend_audss_clksrc = readl(S5P_CLKSRC_AUDSS);
+ i2s->suspend_audss_clkdiv = readl(S5P_CLKDIV_AUDSS);
+ i2s->suspend_audss_clkgate = readl(S5P_CLKGATE_AUDSS);
+ }
+ }
+ return 0;
+}
+
+static int s5p_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+
+ pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n",
+ dai->active, i2s->suspend_iismod, i2s->suspend_iiscon);
+ if (reg_saved_ok != true) {
+ if (dai->id == 0) {
+ writel(i2s->suspend_audss_clksrc, S5P_CLKSRC_AUDSS);
+ writel(i2s->suspend_audss_clkdiv, S5P_CLKDIV_AUDSS);
+ writel(i2s->suspend_audss_clkgate, S5P_CLKGATE_AUDSS);
+ pr_info("Inside %s..@%d\n" , __func__ , __LINE__);
+ }
+ writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+ writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+ writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+ writel(i2s->suspend_iisahb, i2s->regs + S5P_IISAHB);
+ }
+ return 0;
+ /* Is this dai for I2Sv5? */
+ if (dai->id == 0)
+ writel(i2s->suspend_audss_clksrc, S5P_CLKSRC_AUDSS);
+
+ writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ ndelay(250);
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+
+ return 0;
+}
+#else
+#define s3c2412_i2s_suspend NULL
+#define s3c2412_i2s_resume NULL
+#endif /* CONFIG_PM */
+
+int s5p_i2sv5_register_dai(struct device *dev, struct snd_soc_dai_driver *dai)
+{
+ struct snd_soc_dai_ops *ops = dai->ops;
+
+ ops->trigger = s5p_i2s_wr_trigger;
+ ops->hw_params = s5p_i2s_wr_hw_params;
+ ops->set_fmt = s5p_i2s_set_fmt;
+ ops->set_clkdiv = s5p_i2s_set_clkdiv;
+ ops->set_sysclk = s5p_i2s_set_sysclk;
+ ops->startup = s5p_i2s_wr_startup;
+ ops->shutdown = s5p_i2s_wr_shutdown;
+ /* suspend/resume are not necessary due to Clock/Pwer gating scheme */
+ dai->suspend = s5p_i2s_suspend;
+ dai->resume = s5p_i2s_resume;
+ return snd_soc_register_dai(dev, dai);
+}
+
+static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev)
+{
+ struct s3c_audio_pdata *i2s_pdata;
+ struct s3c_i2sv2_info *i2s;
+ struct snd_soc_dai_driver *dai;
+ struct resource *res;
+ struct clk *fout_epll, *mout_epll;
+ struct clk *mout_audss = NULL;
+ unsigned long base;
+ unsigned int iismod;
+ int ret = 0;
+ if (pdev->id >= MAX_I2SV3) {
+ dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ i2s = &s3c64xx_i2s[pdev->id];
+ i2s->dev = &pdev->dev;
+ dai = &s3c64xx_i2s_dai_driver[pdev->id];
+ //dai->dev = &pdev->dev;
+ dai->id = pdev->id;
+ s3c64xx_iis_dai_init(dai);
+
+ i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
+ i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
+ return -ENXIO;
+ }
+ i2s->dma_playback->channel = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
+ return -ENXIO;
+ }
+ i2s->dma_capture->channel = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
+ return -ENXIO;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res),
+ "s3c64xx-i2s")) {
+ dev_err(&pdev->dev, "Unable to request SFR region\n");
+ return -EBUSY;
+ }
+
+ i2s->dma_capture->dma_addr = res->start + S3C2412_IISRXD;
+ i2s->dma_playback->dma_addr = res->start + S3C2412_IISTXD;
+
+ i2s->dma_capture->client = &s3c64xx_dma_client_in;
+ i2s->dma_capture->dma_size = 4;
+ i2s->dma_playback->client = &s3c64xx_dma_client_out;
+ i2s->dma_playback->dma_size = 4;
+
+ i2s_pdata = pdev->dev.platform_data;
+
+ //dai->private_data = i2s;
+ dev_set_drvdata(&pdev->dev, i2s);
+ base = i2s->dma_playback->dma_addr - S3C2412_IISTXD;
+
+ i2s->regs = ioremap(base, 0x100);
+ if (i2s->regs == NULL) {
+ dev_err(&pdev->dev, "cannot ioremap registers\n");
+ return -ENXIO;
+ }
+
+ /* Configure the I2S pins if MUX'ed */
+ if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ return -EINVAL;
+ }
+
+ /* Get i2s power domain regulator */
+ i2s->regulator = regulator_get(&pdev->dev, "pd");
+ if (IS_ERR(i2s->regulator)) {
+ dev_err(&pdev->dev, "%s: failed to get resource %s\n",
+ __func__, "i2s");
+ return PTR_ERR(i2s->regulator);
+ }
+
+ /* Enable Power domain */
+ regulator_enable(i2s->regulator);
+
+ /* Audio Clock
+ * fout_epll >> mout_epll >> sclk_audio
+ * fout_epll >> mout_audss >> audio-bus(iis_clk)
+ * fout_epll >> dout_audio_bus_clk_i2s(iis_busclk)
+ */
+ fout_epll = clk_get(&pdev->dev, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ dev_err(&pdev->dev, "failed to get fout_epll\n");
+ goto err;
+ }
+
+ mout_epll = clk_get(&pdev->dev, "mout_epll");
+ if (IS_ERR(mout_epll)) {
+ dev_err(&pdev->dev, "failed to get mout_epll\n");
+ clk_put(fout_epll);
+ goto err;
+ }
+ clk_set_parent(mout_epll, fout_epll);
+
+ i2s->sclk_audio = clk_get(&pdev->dev, "sclk_audio");
+ if (IS_ERR(i2s->sclk_audio)) {
+ dev_err(&pdev->dev, "failed to get sclk_audio\n");
+ ret = PTR_ERR(i2s->sclk_audio);
+ clk_put(i2s->sclk_audio);
+ goto err;
+ }
+ clk_set_parent(i2s->sclk_audio, mout_epll);
+ /* Need not to enable in general */
+ clk_enable(i2s->sclk_audio);
+
+ /* When I2S V5.1 used, initialize audio subsystem clock */
+ /* CLKMUX_ASS */
+ if (pdev->id == 0) {
+ mout_audss = clk_get(NULL, "mout_audss");
+ if (IS_ERR(mout_audss)) {
+ dev_err(&pdev->dev, "failed to get mout_audss\n");
+ goto err1;
+ }
+ clk_set_parent(mout_audss, fout_epll);
+ /*MUX-I2SA*/
+ i2s->iis_clk = clk_get(&pdev->dev, "audio-bus");
+ if (IS_ERR(i2s->iis_clk)) {
+ dev_err(&pdev->dev, "failed to get audio-bus\n");
+ clk_put(mout_audss);
+ goto err2;
+ }
+ clk_set_parent(i2s->iis_clk, mout_audss);
+ /*getting AUDIO BUS CLK*/
+ i2s->iis_busclk = clk_get(NULL, "dout_audio_bus_clk_i2s");
+ if (IS_ERR(i2s->iis_busclk)) {
+ printk(KERN_ERR "failed to get audss_hclk\n");
+ goto err3;
+ }
+ i2s->iis_ipclk = clk_get(&pdev->dev, "i2s_v50");
+ if (IS_ERR(i2s->iis_ipclk)) {
+ dev_err(&pdev->dev, "failed to get i2s_v50_clock\n");
+ goto err4;
+ }
+ }
+
+#if defined(CONFIG_PLAT_S5P)
+ writel(((1<<0)|(1<<31)), i2s->regs + S3C2412_IISCON);
+#endif
+
+ /* Mark ourselves as in TXRX mode so we can run through our cleanup
+ * process without warnings. */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ iismod |= S3C2412_IISMOD_MODE_TXRX;
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+
+#ifdef CONFIG_S5P_INTERNAL_DMA
+ s5p_i2s_sec_init(i2s->regs, base);
+#endif
+
+ ret = s5p_i2sv5_register_dai(&pdev->dev, dai);
+ if (ret != 0)
+ goto err_i2sv5;
+
+ clk_put(i2s->iis_ipclk);
+ clk_put(i2s->iis_busclk);
+ clk_put(i2s->iis_clk);
+ clk_put(mout_audss);
+ clk_put(mout_epll);
+ clk_put(fout_epll);
+ return 0;
+err4:
+ clk_put(i2s->iis_busclk);
+err3:
+ clk_put(i2s->iis_clk);
+err2:
+ clk_put(mout_audss);
+err1:
+ clk_put(mout_epll);
+ clk_put(fout_epll);
+err_i2sv5:
+ /* Not implemented for I2Sv5 core yet */
+err:
+ iounmap(i2s->regs);
+
+ return ret;
+}
+
+static __devexit int s3c64xx_iis_dev_remove(struct platform_device *pdev)
+{
+ dev_err(&pdev->dev, "Device removal not yet supported\n");
+ return 0;
+}
+
+static struct platform_driver s3c64xx_iis_driver = {
+ .probe = s3c64xx_iis_dev_probe,
+ .remove = s3c64xx_iis_dev_remove,
+ .driver = {
+ .name = "samsung-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c64xx_i2s_init(void)
+{
+ return platform_driver_register(&s3c64xx_iis_driver);
+}
+module_init(s3c64xx_i2s_init);
+
+static void __exit s3c64xx_i2s_exit(void)
+{
+ platform_driver_unregister(&s3c64xx_iis_driver);
+}
+module_exit(s3c64xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S5PC1XX I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s5pc1xx-i2s.h b/sound/soc/samsung/s5pc1xx-i2s.h
new file mode 100644
index 0000000..b754cd5
--- /dev/null
+++ b/sound/soc/samsung/s5pc1xx-i2s.h
@@ -0,0 +1,124 @@
+/* sound/soc/s3c24xx/s3c64xx-i2s.h
+ *
+ * ALSA SoC Audio Layer - S3C64XX I2S driver
+ *
+ * Copyright 2008 Openmoko, Inc.
+ * Copyright 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H
+#define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__
+
+struct clk;
+
+//#include "s3c-i2s-v2.h"
+//==
+
+#define S3C_I2SV2_DIV_BCLK (1)
+#define S3C_I2SV2_DIV_RCLK (2)
+#define S3C_I2SV2_DIV_PRESCALER (3)
+
+/**
+ * struct s3c_i2sv2_info - S3C I2S-V2 information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device registe block.
+ * @master: True if the I2S core is the I2S bit clock master.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ * @suspend_iismod: PM save for the IISMOD register.
+ * @suspend_iiscon: PM save for the IISCON register.
+ * @suspend_iispsr: PM save for the IISPSR register.
+ *
+ * This is the private codec state for the hardware associated with an
+ * I2S channel such as the register mappings and clock sources.
+ */
+struct s3c_i2sv2_info {
+ struct device *dev;
+ void __iomem *regs;
+
+ struct clk *sclk_audio;
+ struct clk *iis_ipclk;
+ struct clk *iis_cclk;
+ struct clk *iis_clk;
+ struct clk *iis_busclk;
+ struct regulator *regulator;
+
+ unsigned char master;
+
+ struct s3c_dma_params *dma_playback;
+ struct s3c_dma_params *dma_capture;
+
+ u32 suspend_iismod;
+ u32 suspend_iiscon;
+ u32 suspend_iispsr;
+ u32 suspend_iisahb;
+ u32 suspend_audss_clksrc;
+ u32 suspend_audss_clkdiv;
+ u32 suspend_audss_clkgate;
+};
+
+struct s3c_i2sv2_rate_calc {
+ unsigned int clk_div; /* for prescaler */
+ unsigned int fs_div; /* for root frame clock */
+};
+
+extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk);
+
+/**
+ * s3c_i2sv2_probe - probe for i2s device helper
+ * @pdev: The platform device supplied to the original probe.
+ * @dai: The ASoC DAI structure supplied to the original probe.
+ * @i2s: Our local i2s structure to fill in.
+ * @base: The base address for the registers.
+ */
+extern int s3c_i2sv2_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s,
+ unsigned long base);
+
+/**
+ * s3c_i2sv2_register_dai - register dai with soc core
+ * @dai: The snd_soc_dai structure to register
+ *
+ * Fill in any missing fields and then register the given dai with the
+ * soc core.
+ */
+extern int s3c_i2sv2_register_dai(struct snd_soc_dai *dai);
+extern void s5p_idma_init(void *);
+
+//==
+
+#define USE_CLKAUDIO 1
+
+#define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK
+#define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK
+#define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER
+
+#define S3C64XX_CLKSRC_PCLK (0)
+#define S3C64XX_CLKSRC_MUX (1)
+#define S3C64XX_CLKSRC_CDCLK (2)
+
+extern struct snd_soc_dai s3c64xx_i2s_dai[];
+
+extern struct snd_soc_dai i2s_sec_fifo_dai;
+extern struct snd_soc_dai i2s_dai;
+extern struct snd_soc_platform s3c_dma_wrapper;
+
+extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai);
+extern int s5p_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai);
+extern int s5p_i2s_startup(struct snd_soc_dai *dai);
+extern int s5p_i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai);
+extern void s5p_i2s_sec_init(void *, dma_addr_t);
+
+#endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */