| /* | 
 |  * s3c24xx-i2s.c  --  ALSA Soc Audio Layer | 
 |  * | 
 |  * (c) 2006 Wolfson Microelectronics PLC. | 
 |  * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com | 
 |  * | 
 |  * (c) 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. | 
 |  * | 
 |  * | 
 |  *  Revision history | 
 |  *    11th Dec 2006   Merged with Simtec driver | 
 |  *    10th Nov 2006   Initial version. | 
 |  */ | 
 |  | 
 | #include <linux/init.h> | 
 | #include <linux/module.h> | 
 | #include <linux/device.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/clk.h> | 
 | #include <sound/driver.h> | 
 | #include <sound/core.h> | 
 | #include <sound/pcm.h> | 
 | #include <sound/pcm_params.h> | 
 | #include <sound/initval.h> | 
 | #include <sound/soc.h> | 
 |  | 
 | #include <asm/hardware.h> | 
 | #include <asm/io.h> | 
 | #include <asm/arch/regs-iis.h> | 
 | #include <asm/arch/regs-gpio.h> | 
 | #include <asm/arch/regs-clock.h> | 
 | #include <asm/arch/audio.h> | 
 | #include <asm/dma.h> | 
 | #include <asm/arch/dma.h> | 
 |  | 
 | #include "s3c24xx-pcm.h" | 
 | #include "s3c24xx-i2s.h" | 
 |  | 
 | #define S3C24XX_I2S_DEBUG 0 | 
 | #if S3C24XX_I2S_DEBUG | 
 | #define DBG(x...) printk(KERN_DEBUG x) | 
 | #else | 
 | #define DBG(x...) | 
 | #endif | 
 |  | 
 | static struct s3c2410_dma_client s3c24xx_dma_client_out = { | 
 | 	.name = "I2S PCM Stereo out" | 
 | }; | 
 |  | 
 | static struct s3c2410_dma_client s3c24xx_dma_client_in = { | 
 | 	.name = "I2S PCM Stereo in" | 
 | }; | 
 |  | 
 | static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = { | 
 | 	.client		= &s3c24xx_dma_client_out, | 
 | 	.channel	= DMACH_I2S_OUT, | 
 | 	.dma_addr	= S3C2410_PA_IIS + S3C2410_IISFIFO, | 
 | 	.dma_size	= 2, | 
 | }; | 
 |  | 
 | static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = { | 
 | 	.client		= &s3c24xx_dma_client_in, | 
 | 	.channel	= DMACH_I2S_IN, | 
 | 	.dma_addr	= S3C2410_PA_IIS + S3C2410_IISFIFO, | 
 | 	.dma_size	= 2, | 
 | }; | 
 |  | 
 | struct s3c24xx_i2s_info { | 
 | 	void __iomem	*regs; | 
 | 	struct clk	*iis_clk; | 
 | }; | 
 | static struct s3c24xx_i2s_info s3c24xx_i2s; | 
 |  | 
 | static void s3c24xx_snd_txctrl(int on) | 
 | { | 
 | 	u32 iisfcon; | 
 | 	u32 iiscon; | 
 | 	u32 iismod; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 |  | 
 | 	DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | 
 |  | 
 | 	if (on) { | 
 | 		iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; | 
 | 		iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; | 
 | 		iiscon  &= ~S3C2410_IISCON_TXIDLE; | 
 | 		iismod  |= S3C2410_IISMOD_TXMODE; | 
 |  | 
 | 		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 	} else { | 
 | 		/* note, we have to disable the FIFOs otherwise bad things | 
 | 		 * seem to happen when the DMA stops. According to the | 
 | 		 * Samsung supplied kernel, this should allow the DMA | 
 | 		 * engine and FIFOs to reset. If this isn't allowed, the | 
 | 		 * DMA engine will simply freeze randomly. | 
 | 		 */ | 
 |  | 
 | 		iisfcon &= ~S3C2410_IISFCON_TXENABLE; | 
 | 		iisfcon &= ~S3C2410_IISFCON_TXDMA; | 
 | 		iiscon  |=  S3C2410_IISCON_TXIDLE; | 
 | 		iiscon  &= ~S3C2410_IISCON_TXDMAEN; | 
 | 		iismod  &= ~S3C2410_IISMOD_TXMODE; | 
 |  | 
 | 		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	} | 
 |  | 
 | 	DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | 
 | } | 
 |  | 
 | static void s3c24xx_snd_rxctrl(int on) | 
 | { | 
 | 	u32 iisfcon; | 
 | 	u32 iiscon; | 
 | 	u32 iismod; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 |  | 
 | 	DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | 
 |  | 
 | 	if (on) { | 
 | 		iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; | 
 | 		iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; | 
 | 		iiscon  &= ~S3C2410_IISCON_RXIDLE; | 
 | 		iismod  |= S3C2410_IISMOD_RXMODE; | 
 |  | 
 | 		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 	} else { | 
 | 		/* note, we have to disable the FIFOs otherwise bad things | 
 | 		 * seem to happen when the DMA stops. According to the | 
 | 		 * Samsung supplied kernel, this should allow the DMA | 
 | 		 * engine and FIFOs to reset. If this isn't allowed, the | 
 | 		 * DMA engine will simply freeze randomly. | 
 | 		 */ | 
 |  | 
 |         iisfcon &= ~S3C2410_IISFCON_RXENABLE; | 
 |         iisfcon &= ~S3C2410_IISFCON_RXDMA; | 
 |         iiscon  |= S3C2410_IISCON_RXIDLE; | 
 |         iiscon  &= ~S3C2410_IISCON_RXDMAEN; | 
 | 		iismod  &= ~S3C2410_IISMOD_RXMODE; | 
 |  | 
 | 		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | 
 | 		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	} | 
 |  | 
 | 	DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | 
 | } | 
 |  | 
 | /* | 
 |  * 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 s3c24xx_snd_lrsync(void) | 
 | { | 
 | 	u32 iiscon; | 
 | 	unsigned long timeout = jiffies + msecs_to_jiffies(5); | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	while (1) { | 
 | 		iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 		if (iiscon & S3C2410_IISCON_LRINDEX) | 
 | 			break; | 
 |  | 
 | 		if (timeout < jiffies) | 
 | 			return -ETIMEDOUT; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Check whether CPU is the master or slave | 
 |  */ | 
 | static inline int s3c24xx_snd_is_clkmaster(void) | 
 | { | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; | 
 | } | 
 |  | 
 | /* | 
 |  * Set S3C24xx I2S DAI format | 
 |  */ | 
 | static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, | 
 | 		unsigned int fmt) | 
 | { | 
 | 	u32 iismod; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	DBG("hw_params r: IISMOD: %lx \n", iismod); | 
 |  | 
 | 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | 
 | 	case SND_SOC_DAIFMT_CBM_CFM: | 
 | 		iismod |= S3C2410_IISMOD_SLAVE; | 
 | 		break; | 
 | 	case SND_SOC_DAIFMT_CBS_CFS: | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | 
 | 	case SND_SOC_DAIFMT_LEFT_J: | 
 | 		iismod |= S3C2410_IISMOD_MSB; | 
 | 		break; | 
 | 	case SND_SOC_DAIFMT_I2S: | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	DBG("hw_params w: IISMOD: %lx \n", iismod); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, | 
 | 				struct snd_pcm_hw_params *params) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
 | 	u32 iismod; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | 
 | 		rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; | 
 | 	else | 
 | 		rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in; | 
 |  | 
 | 	/* Working copies of register */ | 
 | 	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	DBG("hw_params r: IISMOD: %lx\n", iismod); | 
 |  | 
 | 	switch (params_format(params)) { | 
 | 	case SNDRV_PCM_FORMAT_S8: | 
 | 		break; | 
 | 	case SNDRV_PCM_FORMAT_S16_LE: | 
 | 		iismod |= S3C2410_IISMOD_16BIT; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	DBG("hw_params w: IISMOD: %lx\n", iismod); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	switch (cmd) { | 
 | 	case SNDRV_PCM_TRIGGER_START: | 
 | 	case SNDRV_PCM_TRIGGER_RESUME: | 
 | 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | 
 | 		if (!s3c24xx_snd_is_clkmaster()) { | 
 | 			ret = s3c24xx_snd_lrsync(); | 
 | 			if (ret) | 
 | 				goto exit_err; | 
 | 		} | 
 |  | 
 | 		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | 
 | 			s3c24xx_snd_rxctrl(1); | 
 | 		else | 
 | 			s3c24xx_snd_txctrl(1); | 
 | 		break; | 
 | 	case SNDRV_PCM_TRIGGER_STOP: | 
 | 	case SNDRV_PCM_TRIGGER_SUSPEND: | 
 | 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | 
 | 		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | 
 | 			s3c24xx_snd_rxctrl(0); | 
 | 		else | 
 | 			s3c24xx_snd_txctrl(0); | 
 | 		break; | 
 | 	default: | 
 | 		ret = -EINVAL; | 
 | 		break; | 
 | 	} | 
 |  | 
 | exit_err: | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * Set S3C24xx Clock source | 
 |  */ | 
 | static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, | 
 | 	int clk_id, unsigned int freq, int dir) | 
 | { | 
 | 	u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	iismod &= ~S3C2440_IISMOD_MPLL; | 
 |  | 
 | 	switch (clk_id) { | 
 | 	case S3C24XX_CLKSRC_PCLK: | 
 | 		break; | 
 | 	case S3C24XX_CLKSRC_MPLL: | 
 | 		iismod |= S3C2440_IISMOD_MPLL; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Set S3C24xx Clock dividers | 
 |  */ | 
 | static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, | 
 | 	int div_id, int div) | 
 | { | 
 | 	u32 reg; | 
 |  | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	switch (div_id) { | 
 | 	case S3C24XX_DIV_BCLK: | 
 | 		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; | 
 | 		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 		break; | 
 | 	case S3C24XX_DIV_MCLK: | 
 | 		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); | 
 | 		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); | 
 | 		break; | 
 | 	case S3C24XX_DIV_PRESCALER: | 
 | 		writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); | 
 | 		reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 		writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * To avoid duplicating clock code, allow machine driver to | 
 |  * get the clockrate from here. | 
 |  */ | 
 | u32 s3c24xx_i2s_get_clockrate(void) | 
 | { | 
 | 	return clk_get_rate(s3c24xx_i2s.iis_clk); | 
 | } | 
 | EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); | 
 |  | 
 | static int s3c24xx_i2s_probe(struct platform_device *pdev) | 
 | { | 
 | 	DBG("Entered %s\n", __FUNCTION__); | 
 |  | 
 | 	s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); | 
 | 	if (s3c24xx_i2s.regs == NULL) | 
 | 		return -ENXIO; | 
 |  | 
 | 	s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis"); | 
 | 	if (s3c24xx_i2s.iis_clk == NULL) { | 
 | 		DBG("failed to get iis_clock\n"); | 
 | 		iounmap(s3c24xx_i2s.regs); | 
 | 		return -ENODEV; | 
 | 	} | 
 | 	clk_enable(s3c24xx_i2s.iis_clk); | 
 |  | 
 | 	/* Configure the I2S pins in correct mode */ | 
 | 	s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); | 
 | 	s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); | 
 | 	s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); | 
 | 	s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); | 
 | 	s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); | 
 |  | 
 | 	writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); | 
 |  | 
 | 	s3c24xx_snd_txctrl(0); | 
 | 	s3c24xx_snd_rxctrl(0); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | #define S3C24XX_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) | 
 |  | 
 | struct snd_soc_cpu_dai s3c24xx_i2s_dai = { | 
 | 	.name = "s3c24xx-i2s", | 
 | 	.id = 0, | 
 | 	.type = SND_SOC_DAI_I2S, | 
 | 	.probe = s3c24xx_i2s_probe, | 
 | 	.playback = { | 
 | 		.channels_min = 2, | 
 | 		.channels_max = 2, | 
 | 		.rates = S3C24XX_I2S_RATES, | 
 | 		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, | 
 | 	.capture = { | 
 | 		.channels_min = 2, | 
 | 		.channels_max = 2, | 
 | 		.rates = S3C24XX_I2S_RATES, | 
 | 		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, | 
 | 	.ops = { | 
 | 		.trigger = s3c24xx_i2s_trigger, | 
 | 		.hw_params = s3c24xx_i2s_hw_params,}, | 
 | 	.dai_ops = { | 
 | 		.set_fmt = s3c24xx_i2s_set_fmt, | 
 | 		.set_clkdiv = s3c24xx_i2s_set_clkdiv, | 
 | 		.set_sysclk = s3c24xx_i2s_set_sysclk, | 
 | 	}, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai); | 
 |  | 
 | /* Module information */ | 
 | MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); | 
 | MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); | 
 | MODULE_LICENSE("GPL"); |