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