diff options
Diffstat (limited to 'sound/soc/samsung/s5p-i2s_sec.c')
-rw-r--r-- | sound/soc/samsung/s5p-i2s_sec.c | 355 |
1 files changed, 355 insertions, 0 deletions
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"); |