linux/sound/soc/kirkwood/kirkwood-dma.c
<<
>>
Prefs
   1/*
   2 * kirkwood-dma.c
   3 *
   4 * (c) 2010 Arnaud Patard <apatard@mandriva.com>
   5 * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
   6 *
   7 *  This program is free software; you can redistribute  it and/or modify it
   8 *  under  the terms of  the GNU General  Public License as published by the
   9 *  Free Software Foundation;  either version 2 of the  License, or (at your
  10 *  option) any later version.
  11 */
  12
  13#include <linux/init.h>
  14#include <linux/module.h>
  15#include <linux/device.h>
  16#include <linux/io.h>
  17#include <linux/slab.h>
  18#include <linux/interrupt.h>
  19#include <linux/dma-mapping.h>
  20#include <linux/mbus.h>
  21#include <sound/soc.h>
  22#include "kirkwood.h"
  23
  24#define KIRKWOOD_RATES \
  25        (SNDRV_PCM_RATE_44100 | \
  26         SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
  27#define KIRKWOOD_FORMATS \
  28        (SNDRV_PCM_FMTBIT_S16_LE | \
  29         SNDRV_PCM_FMTBIT_S24_LE | \
  30         SNDRV_PCM_FMTBIT_S32_LE)
  31
  32struct kirkwood_dma_priv {
  33        struct snd_pcm_substream *play_stream;
  34        struct snd_pcm_substream *rec_stream;
  35        struct kirkwood_dma_data *data;
  36};
  37
  38static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
  39        .info = (SNDRV_PCM_INFO_INTERLEAVED |
  40                 SNDRV_PCM_INFO_MMAP |
  41                 SNDRV_PCM_INFO_MMAP_VALID |
  42                 SNDRV_PCM_INFO_BLOCK_TRANSFER |
  43                 SNDRV_PCM_INFO_PAUSE),
  44        .formats                = KIRKWOOD_FORMATS,
  45        .rates                  = KIRKWOOD_RATES,
  46        .rate_min               = 44100,
  47        .rate_max               = 96000,
  48        .channels_min           = 1,
  49        .channels_max           = 2,
  50        .buffer_bytes_max       = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
  51        .period_bytes_min       = KIRKWOOD_SND_MIN_PERIOD_BYTES,
  52        .period_bytes_max       = KIRKWOOD_SND_MAX_PERIOD_BYTES,
  53        .periods_min            = KIRKWOOD_SND_MIN_PERIODS,
  54        .periods_max            = KIRKWOOD_SND_MAX_PERIODS,
  55        .fifo_size              = 0,
  56};
  57
  58static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;
  59
  60static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
  61{
  62        struct kirkwood_dma_priv *prdata = dev_id;
  63        struct kirkwood_dma_data *priv = prdata->data;
  64        unsigned long mask, status, cause;
  65
  66        mask = readl(priv->io + KIRKWOOD_INT_MASK);
  67        status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;
  68
  69        cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
  70        if (unlikely(cause)) {
  71                printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
  72                                __func__, cause);
  73                writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
  74                return IRQ_HANDLED;
  75        }
  76
  77        /* we've enabled only bytes interrupts ... */
  78        if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
  79                        KIRKWOOD_INT_CAUSE_REC_BYTES)) {
  80                printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
  81                        __func__, status);
  82                return IRQ_NONE;
  83        }
  84
  85        /* ack int */
  86        writel(status, priv->io + KIRKWOOD_INT_CAUSE);
  87
  88        if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
  89                snd_pcm_period_elapsed(prdata->play_stream);
  90
  91        if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
  92                snd_pcm_period_elapsed(prdata->rec_stream);
  93
  94        return IRQ_HANDLED;
  95}
  96
  97static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
  98                                        unsigned long dma,
  99                                        struct mbus_dram_target_info *dram)
 100{
 101        int i;
 102
 103        /* First disable and clear windows */
 104        writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
 105        writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
 106
 107        /* try to find matching cs for current dma address */
 108        for (i = 0; i < dram->num_cs; i++) {
 109                struct mbus_dram_window *cs = dram->cs + i;
 110                if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
 111                        writel(cs->base & 0xffff0000,
 112                                base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
 113                        writel(((cs->size - 1) & 0xffff0000) |
 114                                (cs->mbus_attr << 8) |
 115                                (dram->mbus_dram_target_id << 4) | 1,
 116                                base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
 117                }
 118        }
 119}
 120
 121static int kirkwood_dma_open(struct snd_pcm_substream *substream)
 122{
 123        int err;
 124        struct snd_pcm_runtime *runtime = substream->runtime;
 125        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 126        struct snd_soc_platform *platform = soc_runtime->platform;
 127        struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
 128        struct kirkwood_dma_data *priv;
 129        struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
 130        unsigned long addr;
 131
 132        priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
 133        snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);
 134
 135        /* Ensure that all constraints linked to dma burst are fullfilled */
 136        err = snd_pcm_hw_constraint_minmax(runtime,
 137                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
 138                        priv->burst * 2,
 139                        KIRKWOOD_AUDIO_BUF_MAX-1);
 140        if (err < 0)
 141                return err;
 142
 143        err = snd_pcm_hw_constraint_step(runtime, 0,
 144                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
 145                        priv->burst);
 146        if (err < 0)
 147                return err;
 148
 149        err = snd_pcm_hw_constraint_step(substream->runtime, 0,
 150                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
 151                         priv->burst);
 152        if (err < 0)
 153                return err;
 154
 155        if (prdata == NULL) {
 156                prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);
 157                if (prdata == NULL)
 158                        return -ENOMEM;
 159
 160                prdata->data = priv;
 161
 162                err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
 163                                  "kirkwood-i2s", prdata);
 164                if (err) {
 165                        kfree(prdata);
 166                        return -EBUSY;
 167                }
 168
 169                snd_soc_platform_set_drvdata(platform, prdata);
 170
 171                /*
 172                 * Enable Error interrupts. We're only ack'ing them but
 173                 * it's usefull for diagnostics
 174                 */
 175                writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
 176        }
 177
 178        addr = virt_to_phys(substream->dma_buffer.area);
 179        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 180                prdata->play_stream = substream;
 181                kirkwood_dma_conf_mbus_windows(priv->io,
 182                        KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);
 183        } else {
 184                prdata->rec_stream = substream;
 185                kirkwood_dma_conf_mbus_windows(priv->io,
 186                        KIRKWOOD_RECORD_WIN, addr, priv->dram);
 187        }
 188
 189        return 0;
 190}
 191
 192static int kirkwood_dma_close(struct snd_pcm_substream *substream)
 193{
 194        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 195        struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
 196        struct snd_soc_platform *platform = soc_runtime->platform;
 197        struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
 198        struct kirkwood_dma_data *priv;
 199
 200        priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
 201
 202        if (!prdata || !priv)
 203                return 0;
 204
 205        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 206                prdata->play_stream = NULL;
 207        else
 208                prdata->rec_stream = NULL;
 209
 210        if (!prdata->play_stream && !prdata->rec_stream) {
 211                writel(0, priv->io + KIRKWOOD_ERR_MASK);
 212                free_irq(priv->irq, prdata);
 213                kfree(prdata);
 214                snd_soc_platform_set_drvdata(platform, NULL);
 215        }
 216
 217        return 0;
 218}
 219
 220static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
 221                                struct snd_pcm_hw_params *params)
 222{
 223        struct snd_pcm_runtime *runtime = substream->runtime;
 224
 225        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 226        runtime->dma_bytes = params_buffer_bytes(params);
 227
 228        return 0;
 229}
 230
 231static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
 232{
 233        snd_pcm_set_runtime_buffer(substream, NULL);
 234        return 0;
 235}
 236
 237static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
 238{
 239        struct snd_pcm_runtime *runtime = substream->runtime;
 240        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 241        struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
 242        struct kirkwood_dma_data *priv;
 243        unsigned long size, count;
 244
 245        priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
 246
 247        /* compute buffer size in term of "words" as requested in specs */
 248        size = frames_to_bytes(runtime, runtime->buffer_size);
 249        size = (size>>2)-1;
 250        count = snd_pcm_lib_period_bytes(substream);
 251
 252        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 253                writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
 254                writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
 255                writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
 256        } else {
 257                writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
 258                writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
 259                writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
 260        }
 261
 262
 263        return 0;
 264}
 265
 266static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
 267                                                *substream)
 268{
 269        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 270        struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
 271        struct kirkwood_dma_data *priv;
 272        snd_pcm_uframes_t count;
 273
 274        priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
 275
 276        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 277                count = bytes_to_frames(substream->runtime,
 278                        readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
 279        else
 280                count = bytes_to_frames(substream->runtime,
 281                        readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));
 282
 283        return count;
 284}
 285
 286struct snd_pcm_ops kirkwood_dma_ops = {
 287        .open =         kirkwood_dma_open,
 288        .close =        kirkwood_dma_close,
 289        .ioctl =        snd_pcm_lib_ioctl,
 290        .hw_params =    kirkwood_dma_hw_params,
 291        .hw_free =      kirkwood_dma_hw_free,
 292        .prepare =      kirkwood_dma_prepare,
 293        .pointer =      kirkwood_dma_pointer,
 294};
 295
 296static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm,
 297                int stream)
 298{
 299        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 300        struct snd_dma_buffer *buf = &substream->dma_buffer;
 301        size_t size = kirkwood_dma_snd_hw.buffer_bytes_max;
 302
 303        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 304        buf->dev.dev = pcm->card->dev;
 305        buf->area = dma_alloc_coherent(pcm->card->dev, size,
 306                        &buf->addr, GFP_KERNEL);
 307        if (!buf->area)
 308                return -ENOMEM;
 309        buf->bytes = size;
 310        buf->private_data = NULL;
 311
 312        return 0;
 313}
 314
 315static int kirkwood_dma_new(struct snd_card *card,
 316                struct snd_soc_dai *dai, struct snd_pcm *pcm)
 317{
 318        int ret;
 319
 320        if (!card->dev->dma_mask)
 321                card->dev->dma_mask = &kirkwood_dma_dmamask;
 322        if (!card->dev->coherent_dma_mask)
 323                card->dev->coherent_dma_mask = 0xffffffff;
 324
 325        if (dai->driver->playback.channels_min) {
 326                ret = kirkwood_dma_preallocate_dma_buffer(pcm,
 327                                SNDRV_PCM_STREAM_PLAYBACK);
 328                if (ret)
 329                        return ret;
 330        }
 331
 332        if (dai->driver->capture.channels_min) {
 333                ret = kirkwood_dma_preallocate_dma_buffer(pcm,
 334                                SNDRV_PCM_STREAM_CAPTURE);
 335                if (ret)
 336                        return ret;
 337        }
 338
 339        return 0;
 340}
 341
 342static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm)
 343{
 344        struct snd_pcm_substream *substream;
 345        struct snd_dma_buffer *buf;
 346        int stream;
 347
 348        for (stream = 0; stream < 2; stream++) {
 349                substream = pcm->streams[stream].substream;
 350                if (!substream)
 351                        continue;
 352                buf = &substream->dma_buffer;
 353                if (!buf->area)
 354                        continue;
 355
 356                dma_free_coherent(pcm->card->dev, buf->bytes,
 357                                buf->area, buf->addr);
 358                buf->area = NULL;
 359        }
 360}
 361
 362static struct snd_soc_platform_driver kirkwood_soc_platform = {
 363        .ops            = &kirkwood_dma_ops,
 364        .pcm_new        = kirkwood_dma_new,
 365        .pcm_free       = kirkwood_dma_free_dma_buffers,
 366};
 367
 368static int __devinit kirkwood_soc_platform_probe(struct platform_device *pdev)
 369{
 370        return snd_soc_register_platform(&pdev->dev, &kirkwood_soc_platform);
 371}
 372
 373static int __devexit kirkwood_soc_platform_remove(struct platform_device *pdev)
 374{
 375        snd_soc_unregister_platform(&pdev->dev);
 376        return 0;
 377}
 378
 379static struct platform_driver kirkwood_pcm_driver = {
 380        .driver = {
 381                        .name = "kirkwood-pcm-audio",
 382                        .owner = THIS_MODULE,
 383        },
 384
 385        .probe = kirkwood_soc_platform_probe,
 386        .remove = __devexit_p(kirkwood_soc_platform_remove),
 387};
 388
 389static int __init kirkwood_pcm_init(void)
 390{
 391        return platform_driver_register(&kirkwood_pcm_driver);
 392}
 393module_init(kirkwood_pcm_init);
 394
 395static void __exit kirkwood_pcm_exit(void)
 396{
 397        platform_driver_unregister(&kirkwood_pcm_driver);
 398}
 399module_exit(kirkwood_pcm_exit);
 400
 401MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
 402MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
 403MODULE_LICENSE("GPL");
 404MODULE_ALIAS("platform:kirkwood-pcm-audio");
 405