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