aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung/s5pc1xx-i2s.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/samsung/s5pc1xx-i2s.c')
-rw-r--r--sound/soc/samsung/s5pc1xx-i2s.c1152
1 files changed, 1152 insertions, 0 deletions
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");