1/* 2 * s3c24xx-i2s.c -- ALSA Soc Audio Layer 3 * 4 * (c) 2006 Wolfson Microelectronics PLC. 5 * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com 6 * 7 * (c) 2004-2005 Simtec Electronics 8 * http://armlinux.simtec.co.uk/ 9 * Ben Dooks <ben@simtec.co.uk> 10 * 11 * This program is free software; you can redistribute it and/or modify it 12 * under the terms of the GNU General Public License as published by the 13 * Free Software Foundation; either version 2 of the License, or (at your 14 * option) any later version. 15 * 16 * 17 * Revision history 18 * 11th Dec 2006 Merged with Simtec driver 19 * 10th Nov 2006 Initial version. 20 */ 21 22#include <linux/init.h> 23#include <linux/module.h> 24#include <linux/device.h> 25#include <linux/delay.h> 26#include <linux/clk.h> 27#include <sound/driver.h> 28#include <sound/core.h> 29#include <sound/pcm.h> 30#include <sound/pcm_params.h> 31#include <sound/initval.h> 32#include <sound/soc.h> 33 34#include <asm/hardware.h> 35#include <asm/io.h> 36#include <asm/arch/regs-iis.h> 37#include <asm/arch/regs-gpio.h> 38#include <asm/arch/regs-clock.h> 39#include <asm/arch/audio.h> 40#include <asm/dma.h> 41#include <asm/arch/dma.h> 42 43#include "s3c24xx-pcm.h" 44#include "s3c24xx-i2s.h" 45 46#define S3C24XX_I2S_DEBUG 0 47#if S3C24XX_I2S_DEBUG 48#define DBG(x...) printk(KERN_DEBUG x) 49#else 50#define DBG(x...) 51#endif 52 53static struct s3c2410_dma_client s3c24xx_dma_client_out = { 54 .name = "I2S PCM Stereo out" 55}; 56 57static struct s3c2410_dma_client s3c24xx_dma_client_in = { 58 .name = "I2S PCM Stereo in" 59}; 60 61static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = { 62 .client = &s3c24xx_dma_client_out, 63 .channel = DMACH_I2S_OUT, 64 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, 65 .dma_size = 2, 66}; 67 68static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = { 69 .client = &s3c24xx_dma_client_in, 70 .channel = DMACH_I2S_IN, 71 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, 72 .dma_size = 2, 73}; 74 75struct s3c24xx_i2s_info { 76 void __iomem *regs; 77 struct clk *iis_clk; 78}; 79static struct s3c24xx_i2s_info s3c24xx_i2s; 80 81static void s3c24xx_snd_txctrl(int on) 82{ 83 u32 iisfcon; 84 u32 iiscon; 85 u32 iismod; 86 87 DBG("Entered %s\n", __FUNCTION__); 88 89 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 90 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 91 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 92 93 DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); 94 95 if (on) { 96 iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; 97 iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; 98 iiscon &= ~S3C2410_IISCON_TXIDLE; 99 iismod |= S3C2410_IISMOD_TXMODE; 100 101 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 102 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 103 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 104 } else { 105 /* note, we have to disable the FIFOs otherwise bad things 106 * seem to happen when the DMA stops. According to the 107 * Samsung supplied kernel, this should allow the DMA 108 * engine and FIFOs to reset. If this isn't allowed, the 109 * DMA engine will simply freeze randomly. 110 */ 111 112 iisfcon &= ~S3C2410_IISFCON_TXENABLE; 113 iisfcon &= ~S3C2410_IISFCON_TXDMA; 114 iiscon |= S3C2410_IISCON_TXIDLE; 115 iiscon &= ~S3C2410_IISCON_TXDMAEN; 116 iismod &= ~S3C2410_IISMOD_TXMODE; 117 118 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 119 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 120 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 121 } 122 123 DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); 124} 125 126static void s3c24xx_snd_rxctrl(int on) 127{ 128 u32 iisfcon; 129 u32 iiscon; 130 u32 iismod; 131 132 DBG("Entered %s\n", __FUNCTION__); 133 134 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 135 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 136 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 137 138 DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); 139 140 if (on) { 141 iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; 142 iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; 143 iiscon &= ~S3C2410_IISCON_RXIDLE; 144 iismod |= S3C2410_IISMOD_RXMODE; 145 146 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 147 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 148 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 149 } else { 150 /* note, we have to disable the FIFOs otherwise bad things 151 * seem to happen when the DMA stops. According to the 152 * Samsung supplied kernel, this should allow the DMA 153 * engine and FIFOs to reset. If this isn't allowed, the 154 * DMA engine will simply freeze randomly. 155 */ 156 157 iisfcon &= ~S3C2410_IISFCON_RXENABLE; 158 iisfcon &= ~S3C2410_IISFCON_RXDMA; 159 iiscon |= S3C2410_IISCON_RXIDLE; 160 iiscon &= ~S3C2410_IISCON_RXDMAEN; 161 iismod &= ~S3C2410_IISMOD_RXMODE; 162 163 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 164 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 165 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 166 } 167 168 DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); 169} 170 171/* 172 * Wait for the LR signal to allow synchronisation to the L/R clock 173 * from the codec. May only be needed for slave mode. 174 */ 175static int s3c24xx_snd_lrsync(void) 176{ 177 u32 iiscon; 178 unsigned long timeout = jiffies + msecs_to_jiffies(5); 179 180 DBG("Entered %s\n", __FUNCTION__); 181 182 while (1) { 183 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 184 if (iiscon & S3C2410_IISCON_LRINDEX) 185 break; 186 187 if (timeout < jiffies) 188 return -ETIMEDOUT; 189 } 190 191 return 0; 192} 193 194/* 195 * Check whether CPU is the master or slave 196 */ 197static inline int s3c24xx_snd_is_clkmaster(void) 198{ 199 DBG("Entered %s\n", __FUNCTION__); 200 201 return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; 202} 203 204/* 205 * Set S3C24xx I2S DAI format 206 */ 207static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, 208 unsigned int fmt) 209{ 210 u32 iismod; 211 212 DBG("Entered %s\n", __FUNCTION__); 213 214 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 215 DBG("hw_params r: IISMOD: %lx \n", iismod); 216 217 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 218 case SND_SOC_DAIFMT_CBM_CFM: 219 iismod |= S3C2410_IISMOD_SLAVE; 220 break; 221 case SND_SOC_DAIFMT_CBS_CFS: 222 break; 223 default: 224 return -EINVAL; 225 } 226 227 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 228 case SND_SOC_DAIFMT_LEFT_J: 229 iismod |= S3C2410_IISMOD_MSB; 230 break; 231 case SND_SOC_DAIFMT_I2S: 232 break; 233 default: 234 return -EINVAL; 235 } 236 237 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 238 DBG("hw_params w: IISMOD: %lx \n", iismod); 239 return 0; 240} 241 242static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, 243 struct snd_pcm_hw_params *params) 244{ 245 struct snd_soc_pcm_runtime *rtd = substream->private_data; 246 u32 iismod; 247 248 DBG("Entered %s\n", __FUNCTION__); 249 250 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 251 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; 252 else 253 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in; 254 255 /* Working copies of register */ 256 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 257 DBG("hw_params r: IISMOD: %lx\n", iismod); 258 259 switch (params_format(params)) { 260 case SNDRV_PCM_FORMAT_S8: 261 break; 262 case SNDRV_PCM_FORMAT_S16_LE: 263 iismod |= S3C2410_IISMOD_16BIT; 264 break; 265 } 266 267 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 268 DBG("hw_params w: IISMOD: %lx\n", iismod); 269 return 0; 270} 271 272static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) 273{ 274 int ret = 0; 275 276 DBG("Entered %s\n", __FUNCTION__); 277 278 switch (cmd) { 279 case SNDRV_PCM_TRIGGER_START: 280 case SNDRV_PCM_TRIGGER_RESUME: 281 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 282 if (!s3c24xx_snd_is_clkmaster()) { 283 ret = s3c24xx_snd_lrsync(); 284 if (ret) 285 goto exit_err; 286 } 287 288 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 289 s3c24xx_snd_rxctrl(1); 290 else 291 s3c24xx_snd_txctrl(1); 292 break; 293 case SNDRV_PCM_TRIGGER_STOP: 294 case SNDRV_PCM_TRIGGER_SUSPEND: 295 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 296 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 297 s3c24xx_snd_rxctrl(0); 298 else 299 s3c24xx_snd_txctrl(0); 300 break; 301 default: 302 ret = -EINVAL; 303 break; 304 } 305 306exit_err: 307 return ret; 308} 309 310/* 311 * Set S3C24xx Clock source 312 */ 313static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, 314 int clk_id, unsigned int freq, int dir) 315{ 316 u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 317 318 DBG("Entered %s\n", __FUNCTION__); 319 320 iismod &= ~S3C2440_IISMOD_MPLL; 321 322 switch (clk_id) { 323 case S3C24XX_CLKSRC_PCLK: 324 break; 325 case S3C24XX_CLKSRC_MPLL: 326 iismod |= S3C2440_IISMOD_MPLL; 327 break; 328 default: 329 return -EINVAL; 330 } 331 332 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 333 return 0; 334} 335 336/* 337 * Set S3C24xx Clock dividers 338 */ 339static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, 340 int div_id, int div) 341{ 342 u32 reg; 343 344 DBG("Entered %s\n", __FUNCTION__); 345 346 switch (div_id) { 347 case S3C24XX_DIV_MCLK: 348 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; 349 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); 350 break; 351 case S3C24XX_DIV_BCLK: 352 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); 353 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); 354 break; 355 case S3C24XX_DIV_PRESCALER: 356 writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); 357 reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 358 writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); 359 break; 360 default: 361 return -EINVAL; 362 } 363 364 return 0; 365} 366 367/* 368 * To avoid duplicating clock code, allow machine driver to 369 * get the clockrate from here. 370 */ 371u32 s3c24xx_i2s_get_clockrate(void) 372{ 373 return clk_get_rate(s3c24xx_i2s.iis_clk); 374} 375EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); 376 377static int s3c24xx_i2s_probe(struct platform_device *pdev) 378{ 379 DBG("Entered %s\n", __FUNCTION__); 380 381 s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); 382 if (s3c24xx_i2s.regs == NULL) 383 return -ENXIO; 384 385 s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis"); 386 if (s3c24xx_i2s.iis_clk == NULL) { 387 DBG("failed to get iis_clock\n"); 388 return -ENODEV; 389 } 390 clk_enable(s3c24xx_i2s.iis_clk); 391 392 /* Configure the I2S pins in correct mode */ 393 s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); 394 s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); 395 s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); 396 s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); 397 s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); 398 399 writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); 400 401 s3c24xx_snd_txctrl(0); 402 s3c24xx_snd_rxctrl(0); 403 404 return 0; 405} 406 407#define S3C24XX_I2S_RATES \ 408 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ 409 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ 410 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) 411 412struct snd_soc_cpu_dai s3c24xx_i2s_dai = { 413 .name = "s3c24xx-i2s", 414 .id = 0, 415 .type = SND_SOC_DAI_I2S, 416 .probe = s3c24xx_i2s_probe, 417 .playback = { 418 .channels_min = 2, 419 .channels_max = 2, 420 .rates = S3C24XX_I2S_RATES, 421 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 422 .capture = { 423 .channels_min = 2, 424 .channels_max = 2, 425 .rates = S3C24XX_I2S_RATES, 426 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 427 .ops = { 428 .trigger = s3c24xx_i2s_trigger, 429 .hw_params = s3c24xx_i2s_hw_params,}, 430 .dai_ops = { 431 .set_fmt = s3c24xx_i2s_set_fmt, 432 .set_clkdiv = s3c24xx_i2s_set_clkdiv, 433 .set_sysclk = s3c24xx_i2s_set_sysclk, 434 }, 435}; 436EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai); 437 438/* Module information */ 439MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); 440MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); 441MODULE_LICENSE("GPL"); 442