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