root/sound/soc/samsung/s3c24xx-i2s.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. s3c24xx_snd_txctrl
  2. s3c24xx_snd_rxctrl
  3. s3c24xx_snd_lrsync
  4. s3c24xx_snd_is_clkmaster
  5. s3c24xx_i2s_set_fmt
  6. s3c24xx_i2s_hw_params
  7. s3c24xx_i2s_trigger
  8. s3c24xx_i2s_set_sysclk
  9. s3c24xx_i2s_set_clkdiv
  10. s3c24xx_i2s_get_clockrate
  11. s3c24xx_i2s_probe
  12. s3c24xx_i2s_suspend
  13. s3c24xx_i2s_resume
  14. s3c24xx_iis_dev_probe

   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");

/* [<][>][^][v][top][bottom][index][help] */