linux/sound/soc/samsung/s3c24xx-i2s.c
<<
>>
Prefs
   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 = samsung_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        samsung_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");
 518