1/* sound/soc/s3c24xx/s3c-pcm.c 2 * 3 * ALSA SoC Audio Layer - S3C PCM-Controller driver 4 * 5 * Copyright (c) 2009 Samsung Electronics Co. Ltd 6 * Author: Jaswinder Singh <jassi.brar@samsung.com> 7 * based upon I2S drivers by Ben Dooks. 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 */ 13 14#include <linux/init.h> 15#include <linux/module.h> 16#include <linux/device.h> 17#include <linux/delay.h> 18#include <linux/clk.h> 19#include <linux/kernel.h> 20#include <linux/gpio.h> 21#include <linux/io.h> 22 23#include <sound/core.h> 24#include <sound/pcm.h> 25#include <sound/pcm_params.h> 26#include <sound/initval.h> 27#include <sound/soc.h> 28 29#include <plat/audio.h> 30#include <plat/dma.h> 31 32#include "s3c-dma.h" 33#include "s3c-pcm.h" 34 35static struct s3c2410_dma_client s3c_pcm_dma_client_out = { 36 .name = "PCM Stereo out" 37}; 38 39static struct s3c2410_dma_client s3c_pcm_dma_client_in = { 40 .name = "PCM Stereo in" 41}; 42 43static struct s3c_dma_params s3c_pcm_stereo_out[] = { 44 [0] = { 45 .client = &s3c_pcm_dma_client_out, 46 .dma_size = 4, 47 }, 48 [1] = { 49 .client = &s3c_pcm_dma_client_out, 50 .dma_size = 4, 51 }, 52}; 53 54static struct s3c_dma_params s3c_pcm_stereo_in[] = { 55 [0] = { 56 .client = &s3c_pcm_dma_client_in, 57 .dma_size = 4, 58 }, 59 [1] = { 60 .client = &s3c_pcm_dma_client_in, 61 .dma_size = 4, 62 }, 63}; 64 65static struct s3c_pcm_info s3c_pcm[2]; 66 67static inline struct s3c_pcm_info *to_info(struct snd_soc_dai *cpu_dai) 68{ 69 return cpu_dai->private_data; 70} 71 72static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on) 73{ 74 void __iomem *regs = pcm->regs; 75 u32 ctl, clkctl; 76 77 clkctl = readl(regs + S3C_PCM_CLKCTL); 78 ctl = readl(regs + S3C_PCM_CTL); 79 ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK 80 << S3C_PCM_CTL_TXDIPSTICK_SHIFT); 81 82 if (on) { 83 ctl |= S3C_PCM_CTL_TXDMA_EN; 84 ctl |= S3C_PCM_CTL_TXFIFO_EN; 85 ctl |= S3C_PCM_CTL_ENABLE; 86 ctl |= (0x20<<S3C_PCM_CTL_TXDIPSTICK_SHIFT); 87 clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 88 } else { 89 ctl &= ~S3C_PCM_CTL_TXDMA_EN; 90 ctl &= ~S3C_PCM_CTL_TXFIFO_EN; 91 92 if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) { 93 ctl &= ~S3C_PCM_CTL_ENABLE; 94 if (!pcm->idleclk) 95 clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 96 } 97 } 98 99 writel(clkctl, regs + S3C_PCM_CLKCTL); 100 writel(ctl, regs + S3C_PCM_CTL); 101} 102 103static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on) 104{ 105 void __iomem *regs = pcm->regs; 106 u32 ctl, clkctl; 107 108 ctl = readl(regs + S3C_PCM_CTL); 109 clkctl = readl(regs + S3C_PCM_CLKCTL); 110 111 if (on) { 112 ctl |= S3C_PCM_CTL_RXDMA_EN; 113 ctl |= S3C_PCM_CTL_RXFIFO_EN; 114 ctl |= S3C_PCM_CTL_ENABLE; 115 clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 116 } else { 117 ctl &= ~S3C_PCM_CTL_RXDMA_EN; 118 ctl &= ~S3C_PCM_CTL_RXFIFO_EN; 119 120 if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) { 121 ctl &= ~S3C_PCM_CTL_ENABLE; 122 if (!pcm->idleclk) 123 clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 124 } 125 } 126 127 writel(clkctl, regs + S3C_PCM_CLKCTL); 128 writel(ctl, regs + S3C_PCM_CTL); 129} 130 131static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd, 132 struct snd_soc_dai *dai) 133{ 134 struct snd_soc_pcm_runtime *rtd = substream->private_data; 135 struct s3c_pcm_info *pcm = to_info(rtd->dai->cpu_dai); 136 unsigned long flags; 137 138 dev_dbg(pcm->dev, "Entered %s\n", __func__); 139 140 switch (cmd) { 141 case SNDRV_PCM_TRIGGER_START: 142 case SNDRV_PCM_TRIGGER_RESUME: 143 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 144 spin_lock_irqsave(&pcm->lock, flags); 145 146 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 147 s3c_pcm_snd_rxctrl(pcm, 1); 148 else 149 s3c_pcm_snd_txctrl(pcm, 1); 150 151 spin_unlock_irqrestore(&pcm->lock, flags); 152 break; 153 154 case SNDRV_PCM_TRIGGER_STOP: 155 case SNDRV_PCM_TRIGGER_SUSPEND: 156 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 157 spin_lock_irqsave(&pcm->lock, flags); 158 159 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 160 s3c_pcm_snd_rxctrl(pcm, 0); 161 else 162 s3c_pcm_snd_txctrl(pcm, 0); 163 164 spin_unlock_irqrestore(&pcm->lock, flags); 165 break; 166 167 default: 168 return -EINVAL; 169 } 170 171 return 0; 172} 173 174static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, 175 struct snd_pcm_hw_params *params, 176 struct snd_soc_dai *socdai) 177{ 178 struct snd_soc_pcm_runtime *rtd = substream->private_data; 179 struct snd_soc_dai_link *dai = rtd->dai; 180 struct s3c_pcm_info *pcm = to_info(dai->cpu_dai); 181 struct s3c_dma_params *dma_data; 182 void __iomem *regs = pcm->regs; 183 struct clk *clk; 184 int sclk_div, sync_div; 185 unsigned long flags; 186 u32 clkctl; 187 188 dev_dbg(pcm->dev, "Entered %s\n", __func__); 189 190 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 191 dma_data = pcm->dma_playback; 192 else 193 dma_data = pcm->dma_capture; 194 195 snd_soc_dai_set_dma_data(dai->cpu_dai, substream, dma_data); 196 197 /* Strictly check for sample size */ 198 switch (params_format(params)) { 199 case SNDRV_PCM_FORMAT_S16_LE: 200 break; 201 default: 202 return -EINVAL; 203 } 204 205 spin_lock_irqsave(&pcm->lock, flags); 206 207 /* Get hold of the PCMSOURCE_CLK */ 208 clkctl = readl(regs + S3C_PCM_CLKCTL); 209 if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK) 210 clk = pcm->pclk; 211 else 212 clk = pcm->cclk; 213 214 /* Set the SCLK divider */ 215 sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs / 216 params_rate(params) / 2 - 1; 217 218 clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK 219 << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); 220 clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK) 221 << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); 222 223 /* Set the SYNC divider */ 224 sync_div = pcm->sclk_per_fs - 1; 225 226 clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK 227 << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); 228 clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK) 229 << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); 230 231 writel(clkctl, regs + S3C_PCM_CLKCTL); 232 233 spin_unlock_irqrestore(&pcm->lock, flags); 234 235 dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n", 236 clk_get_rate(clk), pcm->sclk_per_fs, 237 sclk_div, sync_div); 238 239 return 0; 240} 241 242static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai, 243 unsigned int fmt) 244{ 245 struct s3c_pcm_info *pcm = to_info(cpu_dai); 246 void __iomem *regs = pcm->regs; 247 unsigned long flags; 248 int ret = 0; 249 u32 ctl; 250 251 dev_dbg(pcm->dev, "Entered %s\n", __func__); 252 253 spin_lock_irqsave(&pcm->lock, flags); 254 255 ctl = readl(regs + S3C_PCM_CTL); 256 257 switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 258 case SND_SOC_DAIFMT_NB_NF: 259 /* Nothing to do, NB_NF by default */ 260 break; 261 default: 262 dev_err(pcm->dev, "Unsupported clock inversion!\n"); 263 ret = -EINVAL; 264 goto exit; 265 } 266 267 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 268 case SND_SOC_DAIFMT_CBS_CFS: 269 /* Nothing to do, Master by default */ 270 break; 271 default: 272 dev_err(pcm->dev, "Unsupported master/slave format!\n"); 273 ret = -EINVAL; 274 goto exit; 275 } 276 277 switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { 278 case SND_SOC_DAIFMT_CONT: 279 pcm->idleclk = 1; 280 break; 281 case SND_SOC_DAIFMT_GATED: 282 pcm->idleclk = 0; 283 break; 284 default: 285 dev_err(pcm->dev, "Invalid Clock gating request!\n"); 286 ret = -EINVAL; 287 goto exit; 288 } 289 290 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 291 case SND_SOC_DAIFMT_DSP_A: 292 ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC; 293 ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC; 294 break; 295 case SND_SOC_DAIFMT_DSP_B: 296 ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC; 297 ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC; 298 break; 299 default: 300 dev_err(pcm->dev, "Unsupported data format!\n"); 301 ret = -EINVAL; 302 goto exit; 303 } 304 305 writel(ctl, regs + S3C_PCM_CTL); 306 307exit: 308 spin_unlock_irqrestore(&pcm->lock, flags); 309 310 return ret; 311} 312 313static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai, 314 int div_id, int div) 315{ 316 struct s3c_pcm_info *pcm = to_info(cpu_dai); 317 318 switch (div_id) { 319 case S3C_PCM_SCLK_PER_FS: 320 pcm->sclk_per_fs = div; 321 break; 322 323 default: 324 return -EINVAL; 325 } 326 327 return 0; 328} 329 330static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai, 331 int clk_id, unsigned int freq, int dir) 332{ 333 struct s3c_pcm_info *pcm = to_info(cpu_dai); 334 void __iomem *regs = pcm->regs; 335 u32 clkctl = readl(regs + S3C_PCM_CLKCTL); 336 337 switch (clk_id) { 338 case S3C_PCM_CLKSRC_PCLK: 339 clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK; 340 break; 341 342 case S3C_PCM_CLKSRC_MUX: 343 clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK; 344 345 if (clk_get_rate(pcm->cclk) != freq) 346 clk_set_rate(pcm->cclk, freq); 347 348 break; 349 350 default: 351 return -EINVAL; 352 } 353 354 writel(clkctl, regs + S3C_PCM_CLKCTL); 355 356 return 0; 357} 358 359static struct snd_soc_dai_ops s3c_pcm_dai_ops = { 360 .set_sysclk = s3c_pcm_set_sysclk, 361 .set_clkdiv = s3c_pcm_set_clkdiv, 362 .trigger = s3c_pcm_trigger, 363 .hw_params = s3c_pcm_hw_params, 364 .set_fmt = s3c_pcm_set_fmt, 365}; 366 367#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000 368 369#define S3C_PCM_DECLARE(n) \ 370{ \ 371 .name = "samsung-pcm", \ 372 .id = (n), \ 373 .symmetric_rates = 1, \ 374 .ops = &s3c_pcm_dai_ops, \ 375 .playback = { \ 376 .channels_min = 2, \ 377 .channels_max = 2, \ 378 .rates = S3C_PCM_RATES, \ 379 .formats = SNDRV_PCM_FMTBIT_S16_LE, \ 380 }, \ 381 .capture = { \ 382 .channels_min = 2, \ 383 .channels_max = 2, \ 384 .rates = S3C_PCM_RATES, \ 385 .formats = SNDRV_PCM_FMTBIT_S16_LE, \ 386 }, \ 387} 388 389struct snd_soc_dai s3c_pcm_dai[] = { 390 S3C_PCM_DECLARE(0), 391 S3C_PCM_DECLARE(1), 392}; 393EXPORT_SYMBOL_GPL(s3c_pcm_dai); 394 395static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev) 396{ 397 struct s3c_pcm_info *pcm; 398 struct snd_soc_dai *dai; 399 struct resource *mem_res, *dmatx_res, *dmarx_res; 400 struct s3c_audio_pdata *pcm_pdata; 401 int ret; 402 403 /* Check for valid device index */ 404 if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) { 405 dev_err(&pdev->dev, "id %d out of range\n", pdev->id); 406 return -EINVAL; 407 } 408 409 pcm_pdata = pdev->dev.platform_data; 410 411 /* Check for availability of necessary resource */ 412 dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); 413 if (!dmatx_res) { 414 dev_err(&pdev->dev, "Unable to get PCM-TX dma resource\n"); 415 return -ENXIO; 416 } 417 418 dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1); 419 if (!dmarx_res) { 420 dev_err(&pdev->dev, "Unable to get PCM-RX dma resource\n"); 421 return -ENXIO; 422 } 423 424 mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 425 if (!mem_res) { 426 dev_err(&pdev->dev, "Unable to get register resource\n"); 427 return -ENXIO; 428 } 429 430 if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) { 431 dev_err(&pdev->dev, "Unable to configure gpio\n"); 432 return -EINVAL; 433 } 434 435 pcm = &s3c_pcm[pdev->id]; 436 pcm->dev = &pdev->dev; 437 438 spin_lock_init(&pcm->lock); 439 440 dai = &s3c_pcm_dai[pdev->id]; 441 dai->dev = &pdev->dev; 442 443 /* Default is 128fs */ 444 pcm->sclk_per_fs = 128; 445 446 pcm->cclk = clk_get(&pdev->dev, "audio-bus"); 447 if (IS_ERR(pcm->cclk)) { 448 dev_err(&pdev->dev, "failed to get audio-bus\n"); 449 ret = PTR_ERR(pcm->cclk); 450 goto err1; 451 } 452 clk_enable(pcm->cclk); 453 454 /* record our pcm structure for later use in the callbacks */ 455 dai->private_data = pcm; 456 457 if (!request_mem_region(mem_res->start, 458 resource_size(mem_res), "samsung-pcm")) { 459 dev_err(&pdev->dev, "Unable to request register region\n"); 460 ret = -EBUSY; 461 goto err2; 462 } 463 464 pcm->regs = ioremap(mem_res->start, 0x100); 465 if (pcm->regs == NULL) { 466 dev_err(&pdev->dev, "cannot ioremap registers\n"); 467 ret = -ENXIO; 468 goto err3; 469 } 470 471 pcm->pclk = clk_get(&pdev->dev, "pcm"); 472 if (IS_ERR(pcm->pclk)) { 473 dev_err(&pdev->dev, "failed to get pcm_clock\n"); 474 ret = -ENOENT; 475 goto err4; 476 } 477 clk_enable(pcm->pclk); 478 479 ret = snd_soc_register_dai(dai); 480 if (ret != 0) { 481 dev_err(&pdev->dev, "failed to get pcm_clock\n"); 482 goto err5; 483 } 484 485 s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start 486 + S3C_PCM_RXFIFO; 487 s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start 488 + S3C_PCM_TXFIFO; 489 490 s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start; 491 s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start; 492 493 pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id]; 494 pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id]; 495 496 return 0; 497 498err5: 499 clk_disable(pcm->pclk); 500 clk_put(pcm->pclk); 501err4: 502 iounmap(pcm->regs); 503err3: 504 release_mem_region(mem_res->start, resource_size(mem_res)); 505err2: 506 clk_disable(pcm->cclk); 507 clk_put(pcm->cclk); 508err1: 509 return ret; 510} 511 512static __devexit int s3c_pcm_dev_remove(struct platform_device *pdev) 513{ 514 struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id]; 515 struct resource *mem_res; 516 517 iounmap(pcm->regs); 518 519 mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 520 release_mem_region(mem_res->start, resource_size(mem_res)); 521 522 clk_disable(pcm->cclk); 523 clk_disable(pcm->pclk); 524 clk_put(pcm->pclk); 525 clk_put(pcm->cclk); 526 527 return 0; 528} 529 530static struct platform_driver s3c_pcm_driver = { 531 .probe = s3c_pcm_dev_probe, 532 .remove = s3c_pcm_dev_remove, 533 .driver = { 534 .name = "samsung-pcm", 535 .owner = THIS_MODULE, 536 }, 537}; 538 539static int __init s3c_pcm_init(void) 540{ 541 return platform_driver_register(&s3c_pcm_driver); 542} 543module_init(s3c_pcm_init); 544 545static void __exit s3c_pcm_exit(void) 546{ 547 platform_driver_unregister(&s3c_pcm_driver); 548} 549module_exit(s3c_pcm_exit); 550 551/* Module information */ 552MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>"); 553MODULE_DESCRIPTION("S3C PCM Controller Driver"); 554MODULE_LICENSE("GPL"); 555