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