linux/sound/soc/omap/omap-pcm.c
<<
>>
Prefs
   1/*
   2 * omap-pcm.c  --  ALSA PCM interface for the OMAP SoC
   3 *
   4 * Copyright (C) 2008 Nokia Corporation
   5 *
   6 * Contact: Jarkko Nikula <jhnikula@gmail.com>
   7 *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
   8 *
   9 * This program is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU General Public License
  11 * version 2 as published by the Free Software Foundation.
  12 *
  13 * This program is distributed in the hope that it will be useful, but
  14 * WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16 * General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  21 * 02110-1301 USA
  22 *
  23 */
  24
  25#include <linux/dma-mapping.h>
  26#include <linux/slab.h>
  27#include <sound/core.h>
  28#include <sound/pcm.h>
  29#include <sound/pcm_params.h>
  30#include <sound/soc.h>
  31
  32#include <plat/dma.h>
  33#include "omap-pcm.h"
  34
  35static const struct snd_pcm_hardware omap_pcm_hardware = {
  36        .info                   = SNDRV_PCM_INFO_MMAP |
  37                                  SNDRV_PCM_INFO_MMAP_VALID |
  38                                  SNDRV_PCM_INFO_INTERLEAVED |
  39                                  SNDRV_PCM_INFO_PAUSE |
  40                                  SNDRV_PCM_INFO_RESUME,
  41        .formats                = SNDRV_PCM_FMTBIT_S16_LE |
  42                                  SNDRV_PCM_FMTBIT_S32_LE,
  43        .period_bytes_min       = 32,
  44        .period_bytes_max       = 64 * 1024,
  45        .periods_min            = 2,
  46        .periods_max            = 255,
  47        .buffer_bytes_max       = 128 * 1024,
  48};
  49
  50struct omap_runtime_data {
  51        spinlock_t                      lock;
  52        struct omap_pcm_dma_data        *dma_data;
  53        int                             dma_ch;
  54        int                             period_index;
  55};
  56
  57static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
  58{
  59        struct snd_pcm_substream *substream = data;
  60        struct snd_pcm_runtime *runtime = substream->runtime;
  61        struct omap_runtime_data *prtd = runtime->private_data;
  62        unsigned long flags;
  63
  64        if ((cpu_is_omap1510())) {
  65                /*
  66                 * OMAP1510 doesn't fully support DMA progress counter
  67                 * and there is no software emulation implemented yet,
  68                 * so have to maintain our own progress counters
  69                 * that can be used by omap_pcm_pointer() instead.
  70                 */
  71                spin_lock_irqsave(&prtd->lock, flags);
  72                if ((stat == OMAP_DMA_LAST_IRQ) &&
  73                                (prtd->period_index == runtime->periods - 1)) {
  74                        /* we are in sync, do nothing */
  75                        spin_unlock_irqrestore(&prtd->lock, flags);
  76                        return;
  77                }
  78                if (prtd->period_index >= 0) {
  79                        if (stat & OMAP_DMA_BLOCK_IRQ) {
  80                                /* end of buffer reached, loop back */
  81                                prtd->period_index = 0;
  82                        } else if (stat & OMAP_DMA_LAST_IRQ) {
  83                                /* update the counter for the last period */
  84                                prtd->period_index = runtime->periods - 1;
  85                        } else if (++prtd->period_index >= runtime->periods) {
  86                                /* end of buffer missed? loop back */
  87                                prtd->period_index = 0;
  88                        }
  89                }
  90                spin_unlock_irqrestore(&prtd->lock, flags);
  91        }
  92
  93        snd_pcm_period_elapsed(substream);
  94}
  95
  96/* this may get called several times by oss emulation */
  97static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
  98                              struct snd_pcm_hw_params *params)
  99{
 100        struct snd_pcm_runtime *runtime = substream->runtime;
 101        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 102        struct omap_runtime_data *prtd = runtime->private_data;
 103        struct omap_pcm_dma_data *dma_data;
 104        int err = 0;
 105
 106        dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
 107
 108        /* return if this is a bufferless transfer e.g.
 109         * codec <--> BT codec or GSM modem -- lg FIXME */
 110        if (!dma_data)
 111                return 0;
 112
 113        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 114        runtime->dma_bytes = params_buffer_bytes(params);
 115
 116        if (prtd->dma_data)
 117                return 0;
 118        prtd->dma_data = dma_data;
 119        err = omap_request_dma(dma_data->dma_req, dma_data->name,
 120                               omap_pcm_dma_irq, substream, &prtd->dma_ch);
 121        if (!err) {
 122                /*
 123                 * Link channel with itself so DMA doesn't need any
 124                 * reprogramming while looping the buffer
 125                 */
 126                omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
 127        }
 128
 129        return err;
 130}
 131
 132static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
 133{
 134        struct snd_pcm_runtime *runtime = substream->runtime;
 135        struct omap_runtime_data *prtd = runtime->private_data;
 136
 137        if (prtd->dma_data == NULL)
 138                return 0;
 139
 140        omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
 141        omap_free_dma(prtd->dma_ch);
 142        prtd->dma_data = NULL;
 143
 144        snd_pcm_set_runtime_buffer(substream, NULL);
 145
 146        return 0;
 147}
 148
 149static int omap_pcm_prepare(struct snd_pcm_substream *substream)
 150{
 151        struct snd_pcm_runtime *runtime = substream->runtime;
 152        struct omap_runtime_data *prtd = runtime->private_data;
 153        struct omap_pcm_dma_data *dma_data = prtd->dma_data;
 154        struct omap_dma_channel_params dma_params;
 155        int bytes;
 156
 157        /* return if this is a bufferless transfer e.g.
 158         * codec <--> BT codec or GSM modem -- lg FIXME */
 159        if (!prtd->dma_data)
 160                return 0;
 161
 162        memset(&dma_params, 0, sizeof(dma_params));
 163        dma_params.data_type                    = dma_data->data_type;
 164        dma_params.trigger                      = dma_data->dma_req;
 165        dma_params.sync_mode                    = dma_data->sync_mode;
 166        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 167                dma_params.src_amode            = OMAP_DMA_AMODE_POST_INC;
 168                dma_params.dst_amode            = OMAP_DMA_AMODE_CONSTANT;
 169                dma_params.src_or_dst_synch     = OMAP_DMA_DST_SYNC;
 170                dma_params.src_start            = runtime->dma_addr;
 171                dma_params.dst_start            = dma_data->port_addr;
 172                dma_params.dst_port             = OMAP_DMA_PORT_MPUI;
 173                dma_params.dst_fi               = dma_data->packet_size;
 174        } else {
 175                dma_params.src_amode            = OMAP_DMA_AMODE_CONSTANT;
 176                dma_params.dst_amode            = OMAP_DMA_AMODE_POST_INC;
 177                dma_params.src_or_dst_synch     = OMAP_DMA_SRC_SYNC;
 178                dma_params.src_start            = dma_data->port_addr;
 179                dma_params.dst_start            = runtime->dma_addr;
 180                dma_params.src_port             = OMAP_DMA_PORT_MPUI;
 181                dma_params.src_fi               = dma_data->packet_size;
 182        }
 183        /*
 184         * Set DMA transfer frame size equal to ALSA period size and frame
 185         * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
 186         * we can transfer the whole ALSA buffer with single DMA transfer but
 187         * still can get an interrupt at each period bounary
 188         */
 189        bytes = snd_pcm_lib_period_bytes(substream);
 190        dma_params.elem_count   = bytes >> dma_data->data_type;
 191        dma_params.frame_count  = runtime->periods;
 192        omap_set_dma_params(prtd->dma_ch, &dma_params);
 193
 194        if ((cpu_is_omap1510()))
 195                omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ |
 196                              OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ);
 197        else
 198                omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);
 199
 200        if (!(cpu_class_is_omap1())) {
 201                omap_set_dma_src_burst_mode(prtd->dma_ch,
 202                                                OMAP_DMA_DATA_BURST_16);
 203                omap_set_dma_dest_burst_mode(prtd->dma_ch,
 204                                                OMAP_DMA_DATA_BURST_16);
 205        }
 206
 207        return 0;
 208}
 209
 210static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 211{
 212        struct snd_pcm_runtime *runtime = substream->runtime;
 213        struct omap_runtime_data *prtd = runtime->private_data;
 214        struct omap_pcm_dma_data *dma_data = prtd->dma_data;
 215        unsigned long flags;
 216        int ret = 0;
 217
 218        spin_lock_irqsave(&prtd->lock, flags);
 219        switch (cmd) {
 220        case SNDRV_PCM_TRIGGER_START:
 221        case SNDRV_PCM_TRIGGER_RESUME:
 222        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 223                prtd->period_index = 0;
 224                /* Configure McBSP internal buffer usage */
 225                if (dma_data->set_threshold)
 226                        dma_data->set_threshold(substream);
 227
 228                omap_start_dma(prtd->dma_ch);
 229                break;
 230
 231        case SNDRV_PCM_TRIGGER_STOP:
 232        case SNDRV_PCM_TRIGGER_SUSPEND:
 233        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 234                prtd->period_index = -1;
 235                omap_stop_dma(prtd->dma_ch);
 236                break;
 237        default:
 238                ret = -EINVAL;
 239        }
 240        spin_unlock_irqrestore(&prtd->lock, flags);
 241
 242        return ret;
 243}
 244
 245static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
 246{
 247        struct snd_pcm_runtime *runtime = substream->runtime;
 248        struct omap_runtime_data *prtd = runtime->private_data;
 249        dma_addr_t ptr;
 250        snd_pcm_uframes_t offset;
 251
 252        if (cpu_is_omap1510()) {
 253                offset = prtd->period_index * runtime->period_size;
 254        } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
 255                ptr = omap_get_dma_dst_pos(prtd->dma_ch);
 256                offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
 257        } else {
 258                ptr = omap_get_dma_src_pos(prtd->dma_ch);
 259                offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
 260        }
 261
 262        if (offset >= runtime->buffer_size)
 263                offset = 0;
 264
 265        return offset;
 266}
 267
 268static int omap_pcm_open(struct snd_pcm_substream *substream)
 269{
 270        struct snd_pcm_runtime *runtime = substream->runtime;
 271        struct omap_runtime_data *prtd;
 272        int ret;
 273
 274        snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
 275
 276        /* Ensure that buffer size is a multiple of period size */
 277        ret = snd_pcm_hw_constraint_integer(runtime,
 278                                            SNDRV_PCM_HW_PARAM_PERIODS);
 279        if (ret < 0)
 280                goto out;
 281
 282        prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
 283        if (prtd == NULL) {
 284                ret = -ENOMEM;
 285                goto out;
 286        }
 287        spin_lock_init(&prtd->lock);
 288        runtime->private_data = prtd;
 289
 290out:
 291        return ret;
 292}
 293
 294static int omap_pcm_close(struct snd_pcm_substream *substream)
 295{
 296        struct snd_pcm_runtime *runtime = substream->runtime;
 297
 298        kfree(runtime->private_data);
 299        return 0;
 300}
 301
 302static int omap_pcm_mmap(struct snd_pcm_substream *substream,
 303        struct vm_area_struct *vma)
 304{
 305        struct snd_pcm_runtime *runtime = substream->runtime;
 306
 307        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 308                                     runtime->dma_area,
 309                                     runtime->dma_addr,
 310                                     runtime->dma_bytes);
 311}
 312
 313static struct snd_pcm_ops omap_pcm_ops = {
 314        .open           = omap_pcm_open,
 315        .close          = omap_pcm_close,
 316        .ioctl          = snd_pcm_lib_ioctl,
 317        .hw_params      = omap_pcm_hw_params,
 318        .hw_free        = omap_pcm_hw_free,
 319        .prepare        = omap_pcm_prepare,
 320        .trigger        = omap_pcm_trigger,
 321        .pointer        = omap_pcm_pointer,
 322        .mmap           = omap_pcm_mmap,
 323};
 324
 325static u64 omap_pcm_dmamask = DMA_BIT_MASK(64);
 326
 327static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
 328        int stream)
 329{
 330        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 331        struct snd_dma_buffer *buf = &substream->dma_buffer;
 332        size_t size = omap_pcm_hardware.buffer_bytes_max;
 333
 334        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 335        buf->dev.dev = pcm->card->dev;
 336        buf->private_data = NULL;
 337        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
 338                                           &buf->addr, GFP_KERNEL);
 339        if (!buf->area)
 340                return -ENOMEM;
 341
 342        buf->bytes = size;
 343        return 0;
 344}
 345
 346static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
 347{
 348        struct snd_pcm_substream *substream;
 349        struct snd_dma_buffer *buf;
 350        int stream;
 351
 352        for (stream = 0; stream < 2; stream++) {
 353                substream = pcm->streams[stream].substream;
 354                if (!substream)
 355                        continue;
 356
 357                buf = &substream->dma_buffer;
 358                if (!buf->area)
 359                        continue;
 360
 361                dma_free_writecombine(pcm->card->dev, buf->bytes,
 362                                      buf->area, buf->addr);
 363                buf->area = NULL;
 364        }
 365}
 366
 367static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
 368                 struct snd_pcm *pcm)
 369{
 370        int ret = 0;
 371
 372        if (!card->dev->dma_mask)
 373                card->dev->dma_mask = &omap_pcm_dmamask;
 374        if (!card->dev->coherent_dma_mask)
 375                card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
 376
 377        if (dai->playback.channels_min) {
 378                ret = omap_pcm_preallocate_dma_buffer(pcm,
 379                        SNDRV_PCM_STREAM_PLAYBACK);
 380                if (ret)
 381                        goto out;
 382        }
 383
 384        if (dai->capture.channels_min) {
 385                ret = omap_pcm_preallocate_dma_buffer(pcm,
 386                        SNDRV_PCM_STREAM_CAPTURE);
 387                if (ret)
 388                        goto out;
 389        }
 390
 391out:
 392        return ret;
 393}
 394
 395struct snd_soc_platform omap_soc_platform = {
 396        .name           = "omap-pcm-audio",
 397        .pcm_ops        = &omap_pcm_ops,
 398        .pcm_new        = omap_pcm_new,
 399        .pcm_free       = omap_pcm_free_dma_buffers,
 400};
 401EXPORT_SYMBOL_GPL(omap_soc_platform);
 402
 403static int __init omap_soc_platform_init(void)
 404{
 405        return snd_soc_register_platform(&omap_soc_platform);
 406}
 407module_init(omap_soc_platform_init);
 408
 409static void __exit omap_soc_platform_exit(void)
 410{
 411        snd_soc_unregister_platform(&omap_soc_platform);
 412}
 413module_exit(omap_soc_platform_exit);
 414
 415MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
 416MODULE_DESCRIPTION("OMAP PCM DMA module");
 417MODULE_LICENSE("GPL");
 418
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.