Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.11 517 lines 13 kB view raw
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 "regs-iis.h" 28 29#include "dma.h" 30#include "s3c24xx-i2s.h" 31 32static struct s3c2410_dma_client s3c24xx_dma_client_out = { 33 .name = "I2S PCM Stereo out" 34}; 35 36static struct s3c2410_dma_client s3c24xx_dma_client_in = { 37 .name = "I2S PCM Stereo in" 38}; 39 40static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = { 41 .client = &s3c24xx_dma_client_out, 42 .channel = DMACH_I2S_OUT, 43 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, 44 .dma_size = 2, 45}; 46 47static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = { 48 .client = &s3c24xx_dma_client_in, 49 .channel = DMACH_I2S_IN, 50 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, 51 .dma_size = 2, 52}; 53 54struct s3c24xx_i2s_info { 55 void __iomem *regs; 56 struct clk *iis_clk; 57 u32 iiscon; 58 u32 iismod; 59 u32 iisfcon; 60 u32 iispsr; 61}; 62static struct s3c24xx_i2s_info s3c24xx_i2s; 63 64static void s3c24xx_snd_txctrl(int on) 65{ 66 u32 iisfcon; 67 u32 iiscon; 68 u32 iismod; 69 70 pr_debug("Entered %s\n", __func__); 71 72 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 73 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 74 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 75 76 pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 77 78 if (on) { 79 iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; 80 iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; 81 iiscon &= ~S3C2410_IISCON_TXIDLE; 82 iismod |= S3C2410_IISMOD_TXMODE; 83 84 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 85 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 86 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 87 } else { 88 /* note, we have to disable the FIFOs otherwise bad things 89 * seem to happen when the DMA stops. According to the 90 * Samsung supplied kernel, this should allow the DMA 91 * engine and FIFOs to reset. If this isn't allowed, the 92 * DMA engine will simply freeze randomly. 93 */ 94 95 iisfcon &= ~S3C2410_IISFCON_TXENABLE; 96 iisfcon &= ~S3C2410_IISFCON_TXDMA; 97 iiscon |= S3C2410_IISCON_TXIDLE; 98 iiscon &= ~S3C2410_IISCON_TXDMAEN; 99 iismod &= ~S3C2410_IISMOD_TXMODE; 100 101 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 102 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 103 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 104 } 105 106 pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 107} 108 109static void s3c24xx_snd_rxctrl(int on) 110{ 111 u32 iisfcon; 112 u32 iiscon; 113 u32 iismod; 114 115 pr_debug("Entered %s\n", __func__); 116 117 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 118 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 119 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 120 121 pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 122 123 if (on) { 124 iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; 125 iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; 126 iiscon &= ~S3C2410_IISCON_RXIDLE; 127 iismod |= S3C2410_IISMOD_RXMODE; 128 129 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 130 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 131 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 132 } else { 133 /* note, we have to disable the FIFOs otherwise bad things 134 * seem to happen when the DMA stops. According to the 135 * Samsung supplied kernel, this should allow the DMA 136 * engine and FIFOs to reset. If this isn't allowed, the 137 * DMA engine will simply freeze randomly. 138 */ 139 140 iisfcon &= ~S3C2410_IISFCON_RXENABLE; 141 iisfcon &= ~S3C2410_IISFCON_RXDMA; 142 iiscon |= S3C2410_IISCON_RXIDLE; 143 iiscon &= ~S3C2410_IISCON_RXDMAEN; 144 iismod &= ~S3C2410_IISMOD_RXMODE; 145 146 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 147 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 148 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 149 } 150 151 pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 152} 153 154/* 155 * Wait for the LR signal to allow synchronisation to the L/R clock 156 * from the codec. May only be needed for slave mode. 157 */ 158static int s3c24xx_snd_lrsync(void) 159{ 160 u32 iiscon; 161 int timeout = 50; /* 5ms */ 162 163 pr_debug("Entered %s\n", __func__); 164 165 while (1) { 166 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 167 if (iiscon & S3C2410_IISCON_LRINDEX) 168 break; 169 170 if (!timeout--) 171 return -ETIMEDOUT; 172 udelay(100); 173 } 174 175 return 0; 176} 177 178/* 179 * Check whether CPU is the master or slave 180 */ 181static inline int s3c24xx_snd_is_clkmaster(void) 182{ 183 pr_debug("Entered %s\n", __func__); 184 185 return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; 186} 187 188/* 189 * Set S3C24xx I2S DAI format 190 */ 191static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, 192 unsigned int fmt) 193{ 194 u32 iismod; 195 196 pr_debug("Entered %s\n", __func__); 197 198 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 199 pr_debug("hw_params r: IISMOD: %x \n", iismod); 200 201 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 202 case SND_SOC_DAIFMT_CBM_CFM: 203 iismod |= S3C2410_IISMOD_SLAVE; 204 break; 205 case SND_SOC_DAIFMT_CBS_CFS: 206 iismod &= ~S3C2410_IISMOD_SLAVE; 207 break; 208 default: 209 return -EINVAL; 210 } 211 212 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 213 case SND_SOC_DAIFMT_LEFT_J: 214 iismod |= S3C2410_IISMOD_MSB; 215 break; 216 case SND_SOC_DAIFMT_I2S: 217 iismod &= ~S3C2410_IISMOD_MSB; 218 break; 219 default: 220 return -EINVAL; 221 } 222 223 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 224 pr_debug("hw_params w: IISMOD: %x \n", iismod); 225 return 0; 226} 227 228static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, 229 struct snd_pcm_hw_params *params, 230 struct snd_soc_dai *dai) 231{ 232 struct snd_soc_pcm_runtime *rtd = substream->private_data; 233 struct s3c_dma_params *dma_data; 234 u32 iismod; 235 236 pr_debug("Entered %s\n", __func__); 237 238 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 239 dma_data = &s3c24xx_i2s_pcm_stereo_out; 240 else 241 dma_data = &s3c24xx_i2s_pcm_stereo_in; 242 243 snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data); 244 245 /* Working copies of register */ 246 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 247 pr_debug("hw_params r: IISMOD: %x\n", iismod); 248 249 switch (params_format(params)) { 250 case SNDRV_PCM_FORMAT_S8: 251 iismod &= ~S3C2410_IISMOD_16BIT; 252 dma_data->dma_size = 1; 253 break; 254 case SNDRV_PCM_FORMAT_S16_LE: 255 iismod |= S3C2410_IISMOD_16BIT; 256 dma_data->dma_size = 2; 257 break; 258 default: 259 return -EINVAL; 260 } 261 262 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 263 pr_debug("hw_params w: IISMOD: %x\n", iismod); 264 return 0; 265} 266 267static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 268 struct snd_soc_dai *dai) 269{ 270 int ret = 0; 271 struct s3c_dma_params *dma_data = 272 snd_soc_dai_get_dma_data(dai, substream); 273 274 pr_debug("Entered %s\n", __func__); 275 276 switch (cmd) { 277 case SNDRV_PCM_TRIGGER_START: 278 case SNDRV_PCM_TRIGGER_RESUME: 279 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 280 if (!s3c24xx_snd_is_clkmaster()) { 281 ret = s3c24xx_snd_lrsync(); 282 if (ret) 283 goto exit_err; 284 } 285 286 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 287 s3c24xx_snd_rxctrl(1); 288 else 289 s3c24xx_snd_txctrl(1); 290 291 s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); 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_dai *cpu_dai, 314 int clk_id, unsigned int freq, int dir) 315{ 316 u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 317 318 pr_debug("Entered %s\n", __func__); 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_dai *cpu_dai, 340 int div_id, int div) 341{ 342 u32 reg; 343 344 pr_debug("Entered %s\n", __func__); 345 346 switch (div_id) { 347 case S3C24XX_DIV_BCLK: 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_MCLK: 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 snd_soc_dai *dai) 378{ 379 pr_debug("Entered %s\n", __func__); 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(dai->dev, "iis"); 386 if (IS_ERR(s3c24xx_i2s.iis_clk)) { 387 pr_err("failed to get iis_clock\n"); 388 iounmap(s3c24xx_i2s.regs); 389 return PTR_ERR(s3c24xx_i2s.iis_clk); 390 } 391 clk_enable(s3c24xx_i2s.iis_clk); 392 393 /* Configure the I2S pins (GPE0...GPE4) in correct mode */ 394 s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2), 395 S3C_GPIO_PULL_NONE); 396 397 writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); 398 399 s3c24xx_snd_txctrl(0); 400 s3c24xx_snd_rxctrl(0); 401 402 return 0; 403} 404 405#ifdef CONFIG_PM 406static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai) 407{ 408 pr_debug("Entered %s\n", __func__); 409 410 s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 411 s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 412 s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 413 s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); 414 415 clk_disable(s3c24xx_i2s.iis_clk); 416 417 return 0; 418} 419 420static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai) 421{ 422 pr_debug("Entered %s\n", __func__); 423 clk_enable(s3c24xx_i2s.iis_clk); 424 425 writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 426 writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 427 writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 428 writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); 429 430 return 0; 431} 432#else 433#define s3c24xx_i2s_suspend NULL 434#define s3c24xx_i2s_resume NULL 435#endif 436 437 438#define S3C24XX_I2S_RATES \ 439 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ 440 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ 441 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) 442 443static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = { 444 .trigger = s3c24xx_i2s_trigger, 445 .hw_params = s3c24xx_i2s_hw_params, 446 .set_fmt = s3c24xx_i2s_set_fmt, 447 .set_clkdiv = s3c24xx_i2s_set_clkdiv, 448 .set_sysclk = s3c24xx_i2s_set_sysclk, 449}; 450 451static struct snd_soc_dai_driver s3c24xx_i2s_dai = { 452 .probe = s3c24xx_i2s_probe, 453 .suspend = s3c24xx_i2s_suspend, 454 .resume = s3c24xx_i2s_resume, 455 .playback = { 456 .channels_min = 2, 457 .channels_max = 2, 458 .rates = S3C24XX_I2S_RATES, 459 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 460 .capture = { 461 .channels_min = 2, 462 .channels_max = 2, 463 .rates = S3C24XX_I2S_RATES, 464 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 465 .ops = &s3c24xx_i2s_dai_ops, 466}; 467 468static const struct snd_soc_component_driver s3c24xx_i2s_component = { 469 .name = "s3c24xx-i2s", 470}; 471 472static int s3c24xx_iis_dev_probe(struct platform_device *pdev) 473{ 474 int ret = 0; 475 476 ret = snd_soc_register_component(&pdev->dev, &s3c24xx_i2s_component, 477 &s3c24xx_i2s_dai, 1); 478 if (ret) { 479 pr_err("failed to register the dai\n"); 480 return ret; 481 } 482 483 ret = asoc_dma_platform_register(&pdev->dev); 484 if (ret) { 485 pr_err("failed to register the dma: %d\n", ret); 486 goto err; 487 } 488 489 return 0; 490err: 491 snd_soc_unregister_component(&pdev->dev); 492 return ret; 493} 494 495static int s3c24xx_iis_dev_remove(struct platform_device *pdev) 496{ 497 asoc_dma_platform_unregister(&pdev->dev); 498 snd_soc_unregister_component(&pdev->dev); 499 return 0; 500} 501 502static struct platform_driver s3c24xx_iis_driver = { 503 .probe = s3c24xx_iis_dev_probe, 504 .remove = s3c24xx_iis_dev_remove, 505 .driver = { 506 .name = "s3c24xx-iis", 507 .owner = THIS_MODULE, 508 }, 509}; 510 511module_platform_driver(s3c24xx_iis_driver); 512 513/* Module information */ 514MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); 515MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); 516MODULE_LICENSE("GPL"); 517MODULE_ALIAS("platform:s3c24xx-iis");