linux/sound/soc/samsung/s3c24xx-i2s.c
<<
>>
Prefs
   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
  28static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = {
  29        .chan_name      = "tx",
  30        .addr_width     = 2,
  31};
  32
  33static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = {
  34        .chan_name      = "rx",
  35        .addr_width     = 2,
  36};
  37
  38struct 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};
  46static struct s3c24xx_i2s_info s3c24xx_i2s;
  47
  48static 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
  91static 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 */
 138static 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 */
 159static 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 */
 167static 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
 203static 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
 235static 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
 269exit_err:
 270        return ret;
 271}
 272
 273/*
 274 * Set S3C24xx Clock source
 275 */
 276static 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 */
 300static 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 */
 330u32 s3c24xx_i2s_get_clockrate(void)
 331{
 332        return clk_get_rate(s3c24xx_i2s.iis_clk);
 333}
 334EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
 335
 336static 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
 364static int s3c24xx_i2s_suspend(struct snd_soc_component *component)
 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
 376static int s3c24xx_i2s_resume(struct snd_soc_component *component)
 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
 401static 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
 409static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
 410        .probe = s3c24xx_i2s_probe,
 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        .suspend        = s3c24xx_i2s_suspend,
 427        .resume         = s3c24xx_i2s_resume,
 428};
 429
 430static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
 431{
 432        struct resource *res;
 433        int ret;
 434
 435        s3c24xx_i2s.regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 436        if (IS_ERR(s3c24xx_i2s.regs))
 437                return PTR_ERR(s3c24xx_i2s.regs);
 438
 439        s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
 440        s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;
 441
 442        ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL,
 443                                                 "tx", "rx", NULL);
 444        if (ret) {
 445                dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret);
 446                return ret;
 447        }
 448
 449        ret = devm_snd_soc_register_component(&pdev->dev,
 450                        &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
 451        if (ret)
 452                dev_err(&pdev->dev, "Failed to register the DAI\n");
 453
 454        return ret;
 455}
 456
 457static struct platform_driver s3c24xx_iis_driver = {
 458        .probe  = s3c24xx_iis_dev_probe,
 459        .driver = {
 460                .name = "s3c24xx-iis",
 461        },
 462};
 463
 464module_platform_driver(s3c24xx_iis_driver);
 465
 466/* Module information */
 467MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
 468MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
 469MODULE_LICENSE("GPL");
 470MODULE_ALIAS("platform:s3c24xx-iis");
 471