linux/sound/soc/s6000/s6000-pcm.c
<<
>>
Prefs
   1/*
   2 * ALSA PCM interface for the Stetch s6000 family
   3 *
   4 * Author:      Daniel Gloeckner, <dg@emlix.com>
   5 * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/init.h>
  14#include <linux/platform_device.h>
  15#include <linux/slab.h>
  16#include <linux/dma-mapping.h>
  17#include <linux/interrupt.h>
  18
  19#include <sound/core.h>
  20#include <sound/pcm.h>
  21#include <sound/pcm_params.h>
  22#include <sound/soc.h>
  23
  24#include <asm/dma.h>
  25#include <variant/dmac.h>
  26
  27#include "s6000-pcm.h"
  28
  29#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
  30#define S6_PCM_PREALLOCATE_MAX  (2048 * 1024)
  31
  32static struct snd_pcm_hardware s6000_pcm_hardware = {
  33        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
  34                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
  35                 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),
  36        .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
  37        .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
  38                  SNDRV_PCM_RATE_8000_192000),
  39        .rate_min = 0,
  40        .rate_max = 1562500,
  41        .channels_min = 2,
  42        .channels_max = 8,
  43        .buffer_bytes_max = 0x7ffffff0,
  44        .period_bytes_min = 16,
  45        .period_bytes_max = 0xfffff0,
  46        .periods_min = 2,
  47        .periods_max = 1024, /* no limit */
  48        .fifo_size = 0,
  49};
  50
  51struct s6000_runtime_data {
  52        spinlock_t lock;
  53        int period;             /* current DMA period */
  54};
  55
  56static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)
  57{
  58        struct snd_pcm_runtime *runtime = substream->runtime;
  59        struct s6000_runtime_data *prtd = runtime->private_data;
  60        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
  61        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
  62        int channel;
  63        unsigned int period_size;
  64        unsigned int dma_offset;
  65        dma_addr_t dma_pos;
  66        dma_addr_t src, dst;
  67
  68        period_size = snd_pcm_lib_period_bytes(substream);
  69        dma_offset = prtd->period * period_size;
  70        dma_pos = runtime->dma_addr + dma_offset;
  71
  72        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  73                src = dma_pos;
  74                dst = par->sif_out;
  75                channel = par->dma_out;
  76        } else {
  77                src = par->sif_in;
  78                dst = dma_pos;
  79                channel = par->dma_in;
  80        }
  81
  82        if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
  83                                    DMA_INDEX_CHNL(channel)))
  84                return;
  85
  86        if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
  87                printk(KERN_ERR "s6000-pcm: fifo full\n");
  88                return;
  89        }
  90
  91        BUG_ON(period_size & 15);
  92        s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
  93                        src, dst, period_size);
  94
  95        prtd->period++;
  96        if (unlikely(prtd->period >= runtime->periods))
  97                prtd->period = 0;
  98}
  99
 100static irqreturn_t s6000_pcm_irq(int irq, void *data)
 101{
 102        struct snd_pcm *pcm = data;
 103        struct snd_soc_pcm_runtime *runtime = pcm->private_data;
 104        struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
 105        struct s6000_runtime_data *prtd;
 106        unsigned int has_xrun;
 107        int i, ret = IRQ_NONE;
 108        u32 channel[2] = {
 109                [SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out,
 110                [SNDRV_PCM_STREAM_CAPTURE] = params->dma_in
 111        };
 112
 113        has_xrun = params->check_xrun(runtime->dai->cpu_dai);
 114
 115        for (i = 0; i < ARRAY_SIZE(channel); ++i) {
 116                struct snd_pcm_substream *substream = pcm->streams[i].substream;
 117                unsigned int pending;
 118
 119                if (!channel[i])
 120                        continue;
 121
 122                if (unlikely(has_xrun & (1 << i)) &&
 123                    substream->runtime &&
 124                    snd_pcm_running(substream)) {
 125                        dev_dbg(pcm->dev, "xrun\n");
 126                        snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
 127                        ret = IRQ_HANDLED;
 128                }
 129
 130                pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]),
 131                                             DMA_INDEX_CHNL(channel[i]));
 132
 133                if (pending & 1) {
 134                        ret = IRQ_HANDLED;
 135                        if (likely(substream->runtime &&
 136                                   snd_pcm_running(substream))) {
 137                                snd_pcm_period_elapsed(substream);
 138                                dev_dbg(pcm->dev, "period elapsed %x %x\n",
 139                                       s6dmac_cur_src(DMA_MASK_DMAC(channel[i]),
 140                                                   DMA_INDEX_CHNL(channel[i])),
 141                                       s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]),
 142                                                   DMA_INDEX_CHNL(channel[i])));
 143                                prtd = substream->runtime->private_data;
 144                                spin_lock(&prtd->lock);
 145                                s6000_pcm_enqueue_dma(substream);
 146                                spin_unlock(&prtd->lock);
 147                        }
 148                }
 149
 150                if (unlikely(pending & ~7)) {
 151                        if (pending & (1 << 3))
 152                                printk(KERN_WARNING
 153                                       "s6000-pcm: DMA %x Underflow\n",
 154                                       channel[i]);
 155                        if (pending & (1 << 4))
 156                                printk(KERN_WARNING
 157                                       "s6000-pcm: DMA %x Overflow\n",
 158                                       channel[i]);
 159                        if (pending & 0x1e0)
 160                                printk(KERN_WARNING
 161                                       "s6000-pcm: DMA %x Master Error "
 162                                       "(mask %x)\n",
 163                                       channel[i], pending >> 5);
 164
 165                }
 166        }
 167
 168        return ret;
 169}
 170
 171static int s6000_pcm_start(struct snd_pcm_substream *substream)
 172{
 173        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 174        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 175        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 176        unsigned long flags;
 177        int srcinc;
 178        u32 dma;
 179
 180        spin_lock_irqsave(&prtd->lock, flags);
 181
 182        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 183                srcinc = 1;
 184                dma = par->dma_out;
 185        } else {
 186                srcinc = 0;
 187                dma = par->dma_in;
 188        }
 189        s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
 190                           1 /* priority 1 (0 is max) */,
 191                           0 /* peripheral requests w/o xfer length mode */,
 192                           srcinc /* source address increment */,
 193                           srcinc^1 /* destination address increment */,
 194                           0 /* chunksize 0 (skip impossible on this dma) */,
 195                           0 /* source skip after chunk (impossible) */,
 196                           0 /* destination skip after chunk (impossible) */,
 197                           4 /* 16 byte burst size */,
 198                           -1 /* don't conserve bandwidth */,
 199                           0 /* low watermark irq descriptor theshold */,
 200                           0 /* disable hardware timestamps */,
 201                           1 /* enable channel */);
 202
 203        s6000_pcm_enqueue_dma(substream);
 204        s6000_pcm_enqueue_dma(substream);
 205
 206        spin_unlock_irqrestore(&prtd->lock, flags);
 207
 208        return 0;
 209}
 210
 211static int s6000_pcm_stop(struct snd_pcm_substream *substream)
 212{
 213        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 214        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 215        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 216        unsigned long flags;
 217        u32 channel;
 218
 219        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 220                channel = par->dma_out;
 221        else
 222                channel = par->dma_in;
 223
 224        s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
 225                                  DMA_INDEX_CHNL(channel), 0);
 226
 227        spin_lock_irqsave(&prtd->lock, flags);
 228
 229        s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
 230
 231        spin_unlock_irqrestore(&prtd->lock, flags);
 232
 233        return 0;
 234}
 235
 236static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 237{
 238        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 239        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 240        int ret;
 241
 242        ret = par->trigger(substream, cmd, 0);
 243        if (ret < 0)
 244                return ret;
 245
 246        switch (cmd) {
 247        case SNDRV_PCM_TRIGGER_START:
 248        case SNDRV_PCM_TRIGGER_RESUME:
 249        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 250                ret = s6000_pcm_start(substream);
 251                break;
 252        case SNDRV_PCM_TRIGGER_STOP:
 253        case SNDRV_PCM_TRIGGER_SUSPEND:
 254        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 255                ret = s6000_pcm_stop(substream);
 256                break;
 257        default:
 258                ret = -EINVAL;
 259        }
 260        if (ret < 0)
 261                return ret;
 262
 263        return par->trigger(substream, cmd, 1);
 264}
 265
 266static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
 267{
 268        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 269
 270        prtd->period = 0;
 271
 272        return 0;
 273}
 274
 275static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
 276{
 277        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 278        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 279        struct snd_pcm_runtime *runtime = substream->runtime;
 280        struct s6000_runtime_data *prtd = runtime->private_data;
 281        unsigned long flags;
 282        unsigned int offset;
 283        dma_addr_t count;
 284
 285        spin_lock_irqsave(&prtd->lock, flags);
 286
 287        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 288                count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
 289                                       DMA_INDEX_CHNL(par->dma_out));
 290        else
 291                count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
 292                                       DMA_INDEX_CHNL(par->dma_in));
 293
 294        count -= runtime->dma_addr;
 295
 296        spin_unlock_irqrestore(&prtd->lock, flags);
 297
 298        offset = bytes_to_frames(runtime, count);
 299        if (unlikely(offset >= runtime->buffer_size))
 300                offset = 0;
 301
 302        return offset;
 303}
 304
 305static int s6000_pcm_open(struct snd_pcm_substream *substream)
 306{
 307        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 308        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 309        struct snd_pcm_runtime *runtime = substream->runtime;
 310        struct s6000_runtime_data *prtd;
 311        int ret;
 312
 313        snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
 314
 315        ret = snd_pcm_hw_constraint_step(runtime, 0,
 316                                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
 317        if (ret < 0)
 318                return ret;
 319        ret = snd_pcm_hw_constraint_step(runtime, 0,
 320                                         SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
 321        if (ret < 0)
 322                return ret;
 323        ret = snd_pcm_hw_constraint_integer(runtime,
 324                                            SNDRV_PCM_HW_PARAM_PERIODS);
 325        if (ret < 0)
 326                return ret;
 327
 328        if (par->same_rate) {
 329                int rate;
 330                spin_lock(&par->lock); /* needed? */
 331                rate = par->rate;
 332                spin_unlock(&par->lock);
 333                if (rate != -1) {
 334                        ret = snd_pcm_hw_constraint_minmax(runtime,
 335                                                        SNDRV_PCM_HW_PARAM_RATE,
 336                                                        rate, rate);
 337                        if (ret < 0)
 338                                return ret;
 339                }
 340        }
 341
 342        prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
 343        if (prtd == NULL)
 344                return -ENOMEM;
 345
 346        spin_lock_init(&prtd->lock);
 347
 348        runtime->private_data = prtd;
 349
 350        return 0;
 351}
 352
 353static int s6000_pcm_close(struct snd_pcm_substream *substream)
 354{
 355        struct snd_pcm_runtime *runtime = substream->runtime;
 356        struct s6000_runtime_data *prtd = runtime->private_data;
 357
 358        kfree(prtd);
 359
 360        return 0;
 361}
 362
 363static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
 364                                 struct snd_pcm_hw_params *hw_params)
 365{
 366        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 367        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 368        int ret;
 369        ret = snd_pcm_lib_malloc_pages(substream,
 370                                       params_buffer_bytes(hw_params));
 371        if (ret < 0) {
 372                printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
 373                return ret;
 374        }
 375
 376        if (par->same_rate) {
 377                spin_lock(&par->lock);
 378                if (par->rate == -1 ||
 379                    !(par->in_use & ~(1 << substream->stream))) {
 380                        par->rate = params_rate(hw_params);
 381                        par->in_use |= 1 << substream->stream;
 382                } else if (params_rate(hw_params) != par->rate) {
 383                        snd_pcm_lib_free_pages(substream);
 384                        par->in_use &= ~(1 << substream->stream);
 385                        ret = -EBUSY;
 386                }
 387                spin_unlock(&par->lock);
 388        }
 389        return ret;
 390}
 391
 392static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
 393{
 394        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 395        struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
 396
 397        spin_lock(&par->lock);
 398        par->in_use &= ~(1 << substream->stream);
 399        if (!par->in_use)
 400                par->rate = -1;
 401        spin_unlock(&par->lock);
 402
 403        return snd_pcm_lib_free_pages(substream);
 404}
 405
 406static struct snd_pcm_ops s6000_pcm_ops = {
 407        .open =         s6000_pcm_open,
 408        .close =        s6000_pcm_close,
 409        .ioctl =        snd_pcm_lib_ioctl,
 410        .hw_params =    s6000_pcm_hw_params,
 411        .hw_free =      s6000_pcm_hw_free,
 412        .trigger =      s6000_pcm_trigger,
 413        .prepare =      s6000_pcm_prepare,
 414        .pointer =      s6000_pcm_pointer,
 415};
 416
 417static void s6000_pcm_free(struct snd_pcm *pcm)
 418{
 419        struct snd_soc_pcm_runtime *runtime = pcm->private_data;
 420        struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
 421
 422        free_irq(params->irq, pcm);
 423        snd_pcm_lib_preallocate_free_for_all(pcm);
 424}
 425
 426static u64 s6000_pcm_dmamask = DMA_32BIT_MASK;
 427
 428static int s6000_pcm_new(struct snd_card *card,
 429                         struct snd_soc_dai *dai, struct snd_pcm *pcm)
 430{
 431        struct snd_soc_pcm_runtime *runtime = pcm->private_data;
 432        struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
 433        int res;
 434
 435        if (!card->dev->dma_mask)
 436                card->dev->dma_mask = &s6000_pcm_dmamask;
 437        if (!card->dev->coherent_dma_mask)
 438                card->dev->coherent_dma_mask = DMA_32BIT_MASK;
 439
 440        if (params->dma_in) {
 441                s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
 442                                    DMA_INDEX_CHNL(params->dma_in));
 443                s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
 444                                   DMA_INDEX_CHNL(params->dma_in));
 445        }
 446
 447        if (params->dma_out) {
 448                s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
 449                                    DMA_INDEX_CHNL(params->dma_out));
 450                s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
 451                                   DMA_INDEX_CHNL(params->dma_out));
 452        }
 453
 454        res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
 455                          s6000_soc_platform.name, pcm);
 456        if (res) {
 457                printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
 458                return res;
 459        }
 460
 461        res = snd_pcm_lib_preallocate_pages_for_all(pcm,
 462                                                    SNDRV_DMA_TYPE_DEV,
 463                                                    card->dev,
 464                                                    S6_PCM_PREALLOCATE_SIZE,
 465                                                    S6_PCM_PREALLOCATE_MAX);
 466        if (res)
 467                printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
 468
 469        spin_lock_init(&params->lock);
 470        params->in_use = 0;
 471        params->rate = -1;
 472        return 0;
 473}
 474
 475struct snd_soc_platform s6000_soc_platform = {
 476        .name =         "s6000-audio",
 477        .pcm_ops =      &s6000_pcm_ops,
 478        .pcm_new =      s6000_pcm_new,
 479        .pcm_free =     s6000_pcm_free,
 480};
 481EXPORT_SYMBOL_GPL(s6000_soc_platform);
 482
 483static int __init s6000_pcm_init(void)
 484{
 485        return snd_soc_register_platform(&s6000_soc_platform);
 486}
 487module_init(s6000_pcm_init);
 488
 489static void __exit s6000_pcm_exit(void)
 490{
 491        snd_soc_unregister_platform(&s6000_soc_platform);
 492}
 493module_exit(s6000_pcm_exit);
 494
 495MODULE_AUTHOR("Daniel Gloeckner");
 496MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
 497MODULE_LICENSE("GPL");
 498