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