diff options
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r-- | sound/soc/samsung/Kconfig | 20 | ||||
-rw-r--r-- | sound/soc/samsung/Makefile | 7 | ||||
-rw-r--r-- | sound/soc/samsung/dma.c | 25 | ||||
-rw-r--r-- | sound/soc/samsung/dma.h | 2 | ||||
-rw-r--r-- | sound/soc/samsung/herring-wm8994.c | 358 | ||||
-rw-r--r-- | sound/soc/samsung/i2s.c | 65 | ||||
-rw-r--r-- | sound/soc/samsung/s3c-dma-wrapper.c | 267 | ||||
-rw-r--r-- | sound/soc/samsung/s3c-dma.c | 478 | ||||
-rw-r--r-- | sound/soc/samsung/s3c-dma.h | 33 | ||||
-rw-r--r-- | sound/soc/samsung/s3c-idma.c | 533 | ||||
-rw-r--r-- | sound/soc/samsung/s3c-idma.h | 37 | ||||
-rw-r--r-- | sound/soc/samsung/s5p-i2s_sec.c | 355 | ||||
-rw-r--r-- | sound/soc/samsung/s5pc1xx-i2s.c | 1152 | ||||
-rw-r--r-- | sound/soc/samsung/s5pc1xx-i2s.h | 124 |
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 */ |