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