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;
  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        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
  69
  70        period_size = snd_pcm_lib_period_bytes(substream);
  71        dma_offset = prtd->period * period_size;
  72        dma_pos = runtime->dma_addr + dma_offset;
  73
  74        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  75                src = dma_pos;
  76                dst = par->sif_out;
  77                channel = par->dma_out;
  78        } else {
  79                src = par->sif_in;
  80                dst = dma_pos;
  81                channel = par->dma_in;
  82        }
  83
  84        if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
  85                                    DMA_INDEX_CHNL(channel)))
  86                return;
  87
  88        if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
  89                printk(KERN_ERR "s6000-pcm: fifo full\n");
  90                return;
  91        }
  92
  93        BUG_ON(period_size & 15);
  94        s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
  95                        src, dst, period_size);
  96
  97        prtd->period++;
  98        if (unlikely(prtd->period >= runtime->periods))
  99                prtd->period = 0;
 100}
 101
 102static irqreturn_t s6000_pcm_irq(int irq, void *data)
 103{
 104        struct snd_pcm *pcm = data;
 105        struct snd_soc_pcm_runtime *runtime = pcm->private_data;
 106        struct s6000_runtime_data *prtd;
 107        unsigned int has_xrun;
 108        int i, ret = IRQ_NONE;
 109
 110        for (i = 0; i < 2; ++i) {
 111                struct snd_pcm_substream *substream = pcm->streams[i].substream;
 112                struct s6000_pcm_dma_params *params =
 113                                        snd_soc_dai_get_dma_data(runtime->cpu_dai, substream);
 114                u32 channel;
 115                unsigned int pending;
 116
 117                if (substream == SNDRV_PCM_STREAM_PLAYBACK)
 118                        channel = params->dma_out;
 119                else
 120                        channel = params->dma_in;
 121
 122                has_xrun = params->check_xrun(runtime->cpu_dai);
 123
 124                if (!channel)
 125                        continue;
 126
 127                if (unlikely(has_xrun & (1 << i)) &&
 128                    substream->runtime &&
 129                    snd_pcm_running(substream)) {
 130                        dev_dbg(pcm->dev, "xrun\n");
 131                        snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
 132                        ret = IRQ_HANDLED;
 133                }
 134
 135                pending = s6dmac_int_sources(DMA_MASK_DMAC(channel),
 136                                             DMA_INDEX_CHNL(channel));
 137
 138                if (pending & 1) {
 139                        ret = IRQ_HANDLED;
 140                        if (likely(substream->runtime &&
 141                                   snd_pcm_running(substream))) {
 142                                snd_pcm_period_elapsed(substream);
 143                                dev_dbg(pcm->dev, "period elapsed %x %x\n",
 144                                       s6dmac_cur_src(DMA_MASK_DMAC(channel),
 145                                                   DMA_INDEX_CHNL(channel)),
 146                                       s6dmac_cur_dst(DMA_MASK_DMAC(channel),
 147                                                   DMA_INDEX_CHNL(channel)));
 148                                prtd = substream->runtime->private_data;
 149                                spin_lock(&prtd->lock);
 150                                s6000_pcm_enqueue_dma(substream);
 151                                spin_unlock(&prtd->lock);
 152                        }
 153                }
 154
 155                if (unlikely(pending & ~7)) {
 156                        if (pending & (1 << 3))
 157                                printk(KERN_WARNING
 158                                       "s6000-pcm: DMA %x Underflow\n",
 159                                       channel);
 160                        if (pending & (1 << 4))
 161                                printk(KERN_WARNING
 162                                       "s6000-pcm: DMA %x Overflow\n",
 163                                       channel);
 164                        if (pending & 0x1e0)
 165                                printk(KERN_WARNING
 166                                       "s6000-pcm: DMA %x Master Error "
 167                                       "(mask %x)\n",
 168                                       channel, pending >> 5);
 169
 170                }
 171        }
 172
 173        return ret;
 174}
 175
 176static int s6000_pcm_start(struct snd_pcm_substream *substream)
 177{
 178        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 179        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 180        struct s6000_pcm_dma_params *par;
 181        unsigned long flags;
 182        int srcinc;
 183        u32 dma;
 184
 185        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 186
 187        spin_lock_irqsave(&prtd->lock, flags);
 188
 189        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 190                srcinc = 1;
 191                dma = par->dma_out;
 192        } else {
 193                srcinc = 0;
 194                dma = par->dma_in;
 195        }
 196        s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
 197                           1 /* priority 1 (0 is max) */,
 198                           0 /* peripheral requests w/o xfer length mode */,
 199                           srcinc /* source address increment */,
 200                           srcinc^1 /* destination address increment */,
 201                           0 /* chunksize 0 (skip impossible on this dma) */,
 202                           0 /* source skip after chunk (impossible) */,
 203                           0 /* destination skip after chunk (impossible) */,
 204                           4 /* 16 byte burst size */,
 205                           -1 /* don't conserve bandwidth */,
 206                           0 /* low watermark irq descriptor threshold */,
 207                           0 /* disable hardware timestamps */,
 208                           1 /* enable channel */);
 209
 210        s6000_pcm_enqueue_dma(substream);
 211        s6000_pcm_enqueue_dma(substream);
 212
 213        spin_unlock_irqrestore(&prtd->lock, flags);
 214
 215        return 0;
 216}
 217
 218static int s6000_pcm_stop(struct snd_pcm_substream *substream)
 219{
 220        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 221        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 222        struct s6000_pcm_dma_params *par;
 223        unsigned long flags;
 224        u32 channel;
 225
 226        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 227
 228        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 229                channel = par->dma_out;
 230        else
 231                channel = par->dma_in;
 232
 233        s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
 234                                  DMA_INDEX_CHNL(channel), 0);
 235
 236        spin_lock_irqsave(&prtd->lock, flags);
 237
 238        s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
 239
 240        spin_unlock_irqrestore(&prtd->lock, flags);
 241
 242        return 0;
 243}
 244
 245static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 246{
 247        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 248        struct s6000_pcm_dma_params *par;
 249        int ret;
 250
 251        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 252
 253        ret = par->trigger(substream, cmd, 0);
 254        if (ret < 0)
 255                return ret;
 256
 257        switch (cmd) {
 258        case SNDRV_PCM_TRIGGER_START:
 259        case SNDRV_PCM_TRIGGER_RESUME:
 260        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 261                ret = s6000_pcm_start(substream);
 262                break;
 263        case SNDRV_PCM_TRIGGER_STOP:
 264        case SNDRV_PCM_TRIGGER_SUSPEND:
 265        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 266                ret = s6000_pcm_stop(substream);
 267                break;
 268        default:
 269                ret = -EINVAL;
 270        }
 271        if (ret < 0)
 272                return ret;
 273
 274        return par->trigger(substream, cmd, 1);
 275}
 276
 277static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
 278{
 279        struct s6000_runtime_data *prtd = substream->runtime->private_data;
 280
 281        prtd->period = 0;
 282
 283        return 0;
 284}
 285
 286static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
 287{
 288        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 289        struct s6000_pcm_dma_params *par;
 290        struct snd_pcm_runtime *runtime = substream->runtime;
 291        struct s6000_runtime_data *prtd = runtime->private_data;
 292        unsigned long flags;
 293        unsigned int offset;
 294        dma_addr_t count;
 295
 296        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 297
 298        spin_lock_irqsave(&prtd->lock, flags);
 299
 300        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 301                count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
 302                                       DMA_INDEX_CHNL(par->dma_out));
 303        else
 304                count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
 305                                       DMA_INDEX_CHNL(par->dma_in));
 306
 307        count -= runtime->dma_addr;
 308
 309        spin_unlock_irqrestore(&prtd->lock, flags);
 310
 311        offset = bytes_to_frames(runtime, count);
 312        if (unlikely(offset >= runtime->buffer_size))
 313                offset = 0;
 314
 315        return offset;
 316}
 317
 318static int s6000_pcm_open(struct snd_pcm_substream *substream)
 319{
 320        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 321        struct s6000_pcm_dma_params *par;
 322        struct snd_pcm_runtime *runtime = substream->runtime;
 323        struct s6000_runtime_data *prtd;
 324        int ret;
 325
 326        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 327        snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
 328
 329        ret = snd_pcm_hw_constraint_step(runtime, 0,
 330                                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
 331        if (ret < 0)
 332                return ret;
 333        ret = snd_pcm_hw_constraint_step(runtime, 0,
 334                                         SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
 335        if (ret < 0)
 336                return ret;
 337        ret = snd_pcm_hw_constraint_integer(runtime,
 338                                            SNDRV_PCM_HW_PARAM_PERIODS);
 339        if (ret < 0)
 340                return ret;
 341
 342        if (par->same_rate) {
 343                int rate;
 344                spin_lock(&par->lock); /* needed? */
 345                rate = par->rate;
 346                spin_unlock(&par->lock);
 347                if (rate != -1) {
 348                        ret = snd_pcm_hw_constraint_minmax(runtime,
 349                                                        SNDRV_PCM_HW_PARAM_RATE,
 350                                                        rate, rate);
 351                        if (ret < 0)
 352                                return ret;
 353                }
 354        }
 355
 356        prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
 357        if (prtd == NULL)
 358                return -ENOMEM;
 359
 360        spin_lock_init(&prtd->lock);
 361
 362        runtime->private_data = prtd;
 363
 364        return 0;
 365}
 366
 367static int s6000_pcm_close(struct snd_pcm_substream *substream)
 368{
 369        struct snd_pcm_runtime *runtime = substream->runtime;
 370        struct s6000_runtime_data *prtd = runtime->private_data;
 371
 372        kfree(prtd);
 373
 374        return 0;
 375}
 376
 377static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
 378                                 struct snd_pcm_hw_params *hw_params)
 379{
 380        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 381        struct s6000_pcm_dma_params *par;
 382        int ret;
 383        ret = snd_pcm_lib_malloc_pages(substream,
 384                                       params_buffer_bytes(hw_params));
 385        if (ret < 0) {
 386                printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
 387                return ret;
 388        }
 389
 390        par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 391
 392        if (par->same_rate) {
 393                spin_lock(&par->lock);
 394                if (par->rate == -1 ||
 395                    !(par->in_use & ~(1 << substream->stream))) {
 396                        par->rate = params_rate(hw_params);
 397                        par->in_use |= 1 << substream->stream;
 398                } else if (params_rate(hw_params) != par->rate) {
 399                        snd_pcm_lib_free_pages(substream);
 400                        par->in_use &= ~(1 << substream->stream);
 401                        ret = -EBUSY;
 402                }
 403                spin_unlock(&par->lock);
 404        }
 405        return ret;
 406}
 407
 408static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
 409{
 410        struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
 411        struct s6000_pcm_dma_params *par =
 412                snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
 413
 414        spin_lock(&par->lock);
 415        par->in_use &= ~(1 << substream->stream);
 416        if (!par->in_use)
 417                par->rate = -1;
 418        spin_unlock(&par->lock);
 419
 420        return snd_pcm_lib_free_pages(substream);
 421}
 422
 423static struct snd_pcm_ops s6000_pcm_ops = {
 424        .open =         s6000_pcm_open,
 425        .close =        s6000_pcm_close,
 426        .ioctl =        snd_pcm_lib_ioctl,
 427        .hw_params =    s6000_pcm_hw_params,
 428        .hw_free =      s6000_pcm_hw_free,
 429        .trigger =      s6000_pcm_trigger,
 430        .prepare =      s6000_pcm_prepare,
 431        .pointer =      s6000_pcm_pointer,
 432};
 433
 434static void s6000_pcm_free(struct snd_pcm *pcm)
 435{
 436        struct snd_soc_pcm_runtime *runtime = pcm->private_data;
 437        struct s6000_pcm_dma_params *params =
 438                snd_soc_dai_get_dma_data(runtime->cpu_dai,
 439                        pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream);
 440
 441        free_irq(params->irq, pcm);
 442        snd_pcm_lib_preallocate_free_for_all(pcm);
 443}
 444
 445static u64 s6000_pcm_dmamask = DMA_BIT_MASK(32);
 446
 447static int s6000_pcm_new(struct snd_soc_pcm_runtime *runtime)
 448{
 449        struct snd_card *card = runtime->card->snd_card;
 450        struct snd_pcm *pcm = runtime->pcm;
 451        struct s6000_pcm_dma_params *params;
 452        int res;
 453
 454        params = snd_soc_dai_get_dma_data(runtime->cpu_dai,
 455                        pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream);
 456
 457        if (!card->dev->dma_mask)
 458                card->dev->dma_mask = &s6000_pcm_dmamask;
 459        if (!card->dev->coherent_dma_mask)
 460                card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
 461
 462        if (params->dma_in) {
 463                s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
 464                                    DMA_INDEX_CHNL(params->dma_in));
 465                s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
 466                                   DMA_INDEX_CHNL(params->dma_in));
 467        }
 468
 469        if (params->dma_out) {
 470                s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
 471                                    DMA_INDEX_CHNL(params->dma_out));
 472                s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
 473                                   DMA_INDEX_CHNL(params->dma_out));
 474        }
 475
 476        res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
 477                          "s6000-audio", pcm);
 478        if (res) {
 479                printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
 480                return res;
 481        }
 482
 483        res = snd_pcm_lib_preallocate_pages_for_all(pcm,
 484                                                    SNDRV_DMA_TYPE_DEV,
 485                                                    card->dev,
 486                                                    S6_PCM_PREALLOCATE_SIZE,
 487                                                    S6_PCM_PREALLOCATE_MAX);
 488        if (res)
 489                printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
 490
 491        spin_lock_init(&params->lock);
 492        params->in_use = 0;
 493        params->rate = -1;
 494        return 0;
 495}
 496
 497static struct snd_soc_platform_driver s6000_soc_platform = {
 498        .ops =          &s6000_pcm_ops,
 499        .pcm_new =      s6000_pcm_new,
 500        .pcm_free =     s6000_pcm_free,
 501};
 502
 503static int s6000_soc_platform_probe(struct platform_device *pdev)
 504{
 505        return snd_soc_register_platform(&pdev->dev, &s6000_soc_platform);
 506}
 507
 508static int s6000_soc_platform_remove(struct platform_device *pdev)
 509{
 510        snd_soc_unregister_platform(&pdev->dev);
 511        return 0;
 512}
 513
 514static struct platform_driver s6000_pcm_driver = {
 515        .driver = {
 516                        .name = "s6000-pcm-audio",
 517                        .owner = THIS_MODULE,
 518        },
 519
 520        .probe = s6000_soc_platform_probe,
 521        .remove = s6000_soc_platform_remove,
 522};
 523
 524module_platform_driver(s6000_pcm_driver);
 525
 526MODULE_AUTHOR("Daniel Gloeckner");
 527MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
 528MODULE_LICENSE("GPL");
 529