linux/sound/soc/ep93xx/ep93xx-pcm.c
<<
>>
Prefs
   1/*
   2 * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
   3 *
   4 * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
   5 * Copyright (C) 2006 Applied Data Systems
   6 *
   7 * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
   8 *   Copyright (c) 2008 Ryan Mallon
   9 *
  10 * This program is free software; you can redistribute it and/or modify
  11 * it under the terms of the GNU General Public License version 2 as
  12 * published by the Free Software Foundation.
  13 */
  14
  15#include <linux/module.h>
  16#include <linux/init.h>
  17#include <linux/device.h>
  18#include <linux/slab.h>
  19#include <linux/dmaengine.h>
  20#include <linux/dma-mapping.h>
  21
  22#include <sound/core.h>
  23#include <sound/pcm.h>
  24#include <sound/pcm_params.h>
  25#include <sound/soc.h>
  26
  27#include <mach/dma.h>
  28#include <mach/hardware.h>
  29#include <mach/ep93xx-regs.h>
  30
  31#include "ep93xx-pcm.h"
  32
  33static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
  34        .info                   = (SNDRV_PCM_INFO_MMAP          |
  35                                   SNDRV_PCM_INFO_MMAP_VALID    |
  36                                   SNDRV_PCM_INFO_INTERLEAVED   |
  37                                   SNDRV_PCM_INFO_BLOCK_TRANSFER),
  38                                   
  39        .rates                  = SNDRV_PCM_RATE_8000_192000,
  40        .rate_min               = SNDRV_PCM_RATE_8000,
  41        .rate_max               = SNDRV_PCM_RATE_192000,
  42        
  43        .formats                = (SNDRV_PCM_FMTBIT_S16_LE |
  44                                   SNDRV_PCM_FMTBIT_S24_LE |
  45                                   SNDRV_PCM_FMTBIT_S32_LE),
  46        
  47        .buffer_bytes_max       = 131072,
  48        .period_bytes_min       = 32,
  49        .period_bytes_max       = 32768,
  50        .periods_min            = 1,
  51        .periods_max            = 32,
  52        .fifo_size              = 32,
  53};
  54
  55struct ep93xx_runtime_data
  56{
  57        int                             pointer_bytes;
  58        int                             periods;
  59        int                             period_bytes;
  60        struct dma_chan                 *dma_chan;
  61        struct ep93xx_dma_data          dma_data;
  62};
  63
  64static void ep93xx_pcm_dma_callback(void *data)
  65{
  66        struct snd_pcm_substream *substream = data;
  67        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
  68
  69        rtd->pointer_bytes += rtd->period_bytes;
  70        rtd->pointer_bytes %= rtd->period_bytes * rtd->periods;
  71
  72        snd_pcm_period_elapsed(substream);
  73}
  74
  75static bool ep93xx_pcm_dma_filter(struct dma_chan *chan, void *filter_param)
  76{
  77        struct ep93xx_dma_data *data = filter_param;
  78
  79        if (data->direction == ep93xx_dma_chan_direction(chan)) {
  80                chan->private = data;
  81                return true;
  82        }
  83
  84        return false;
  85}
  86
  87static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
  88{
  89        struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
  90        struct snd_soc_dai *cpu_dai = soc_rtd->cpu_dai;
  91        struct ep93xx_pcm_dma_params *dma_params;
  92        struct ep93xx_runtime_data *rtd;    
  93        dma_cap_mask_t mask;
  94        int ret;
  95
  96        ret = snd_pcm_hw_constraint_integer(substream->runtime,
  97                                            SNDRV_PCM_HW_PARAM_PERIODS);
  98        if (ret < 0)
  99                return ret;
 100
 101        snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
 102
 103        rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
 104        if (!rtd) 
 105                return -ENOMEM;
 106
 107        dma_cap_zero(mask);
 108        dma_cap_set(DMA_SLAVE, mask);
 109        dma_cap_set(DMA_CYCLIC, mask);
 110
 111        dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
 112        rtd->dma_data.port = dma_params->dma_port;
 113        rtd->dma_data.name = dma_params->name;
 114
 115        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 116                rtd->dma_data.direction = DMA_MEM_TO_DEV;
 117        else
 118                rtd->dma_data.direction = DMA_DEV_TO_MEM;
 119
 120        rtd->dma_chan = dma_request_channel(mask, ep93xx_pcm_dma_filter,
 121                                            &rtd->dma_data);
 122        if (!rtd->dma_chan) {
 123                kfree(rtd);
 124                return -EINVAL;
 125        }
 126        
 127        substream->runtime->private_data = rtd;
 128        return 0;
 129}
 130
 131static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
 132{
 133        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
 134
 135        dma_release_channel(rtd->dma_chan);
 136        kfree(rtd);
 137        return 0;
 138}
 139
 140static int ep93xx_pcm_dma_submit(struct snd_pcm_substream *substream)
 141{
 142        struct snd_pcm_runtime *runtime = substream->runtime;
 143        struct ep93xx_runtime_data *rtd = runtime->private_data;
 144        struct dma_chan *chan = rtd->dma_chan;
 145        struct dma_device *dma_dev = chan->device;
 146        struct dma_async_tx_descriptor *desc;
 147
 148        rtd->pointer_bytes = 0;
 149        desc = dma_dev->device_prep_dma_cyclic(chan, runtime->dma_addr,
 150                                               rtd->period_bytes * rtd->periods,
 151                                               rtd->period_bytes,
 152                                               rtd->dma_data.direction);
 153        if (!desc)
 154                return -EINVAL;
 155
 156        desc->callback = ep93xx_pcm_dma_callback;
 157        desc->callback_param = substream;
 158
 159        dmaengine_submit(desc);
 160        return 0;
 161}
 162
 163static void ep93xx_pcm_dma_flush(struct snd_pcm_substream *substream)
 164{
 165        struct snd_pcm_runtime *runtime = substream->runtime;
 166        struct ep93xx_runtime_data *rtd = runtime->private_data;
 167
 168        dmaengine_terminate_all(rtd->dma_chan);
 169}
 170
 171static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
 172                                struct snd_pcm_hw_params *params)
 173{
 174        struct snd_pcm_runtime *runtime = substream->runtime;
 175        struct ep93xx_runtime_data *rtd = runtime->private_data;
 176
 177        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 178
 179        rtd->periods = params_periods(params);
 180        rtd->period_bytes = params_period_bytes(params);
 181        return 0;
 182}
 183
 184static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
 185{
 186        snd_pcm_set_runtime_buffer(substream, NULL);
 187        return 0;
 188}
 189
 190static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 191{
 192        int ret;
 193
 194        ret = 0;
 195        switch (cmd) {
 196        case SNDRV_PCM_TRIGGER_START:
 197        case SNDRV_PCM_TRIGGER_RESUME:
 198        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 199                ret = ep93xx_pcm_dma_submit(substream);
 200                break;
 201
 202        case SNDRV_PCM_TRIGGER_STOP:
 203        case SNDRV_PCM_TRIGGER_SUSPEND:
 204        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 205                ep93xx_pcm_dma_flush(substream);
 206                break;
 207
 208        default:
 209                ret = -EINVAL;
 210                break;
 211        }
 212
 213        return ret;
 214}
 215
 216static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
 217{
 218        struct snd_pcm_runtime *runtime = substream->runtime;
 219        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
 220
 221        /* FIXME: implement this with sub-period granularity */
 222        return bytes_to_frames(runtime, rtd->pointer_bytes);
 223}
 224
 225static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream,
 226                           struct vm_area_struct *vma)
 227{
 228        struct snd_pcm_runtime *runtime = substream->runtime;
 229
 230        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 231                                     runtime->dma_area,
 232                                     runtime->dma_addr,
 233                                     runtime->dma_bytes);
 234}
 235
 236static struct snd_pcm_ops ep93xx_pcm_ops = {
 237        .open           = ep93xx_pcm_open,
 238        .close          = ep93xx_pcm_close,
 239        .ioctl          = snd_pcm_lib_ioctl,
 240        .hw_params      = ep93xx_pcm_hw_params,
 241        .hw_free        = ep93xx_pcm_hw_free,
 242        .trigger        = ep93xx_pcm_trigger,
 243        .pointer        = ep93xx_pcm_pointer,
 244        .mmap           = ep93xx_pcm_mmap,
 245};
 246
 247static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 248{
 249        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 250        struct snd_dma_buffer *buf = &substream->dma_buffer;
 251        size_t size = ep93xx_pcm_hardware.buffer_bytes_max;
 252
 253        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 254        buf->dev.dev = pcm->card->dev;
 255        buf->private_data = NULL;
 256        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
 257                                           &buf->addr, GFP_KERNEL);
 258        buf->bytes = size;
 259
 260        return (buf->area == NULL) ? -ENOMEM : 0;
 261}
 262
 263static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
 264{
 265        struct snd_pcm_substream *substream;
 266        struct snd_dma_buffer *buf;
 267        int stream;
 268
 269        for (stream = 0; stream < 2; stream++) {                
 270                substream = pcm->streams[stream].substream;
 271                if (!substream)
 272                        continue;
 273                
 274                buf = &substream->dma_buffer;
 275                if (!buf->area)
 276                        continue;
 277
 278                dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area,
 279                                      buf->addr);
 280                buf->area = NULL;
 281        }
 282}
 283
 284static u64 ep93xx_pcm_dmamask = 0xffffffff;
 285
 286static int ep93xx_pcm_new(struct snd_soc_pcm_runtime *rtd)
 287{
 288        struct snd_card *card = rtd->card->snd_card;
 289        struct snd_pcm *pcm = rtd->pcm;
 290        int ret = 0;
 291
 292        if (!card->dev->dma_mask)
 293                card->dev->dma_mask = &ep93xx_pcm_dmamask;
 294        if (!card->dev->coherent_dma_mask)
 295                card->dev->coherent_dma_mask = 0xffffffff;
 296
 297        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 298                ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
 299                                        SNDRV_PCM_STREAM_PLAYBACK);
 300                if (ret)
 301                        return ret;
 302        }
 303
 304        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 305                ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
 306                                        SNDRV_PCM_STREAM_CAPTURE);
 307                if (ret)
 308                        return ret;
 309        }
 310
 311        return 0;
 312}
 313
 314static struct snd_soc_platform_driver ep93xx_soc_platform = {
 315        .ops            = &ep93xx_pcm_ops,
 316        .pcm_new        = &ep93xx_pcm_new,
 317        .pcm_free       = &ep93xx_pcm_free_dma_buffers,
 318};
 319
 320static int __devinit ep93xx_soc_platform_probe(struct platform_device *pdev)
 321{
 322        return snd_soc_register_platform(&pdev->dev, &ep93xx_soc_platform);
 323}
 324
 325static int __devexit ep93xx_soc_platform_remove(struct platform_device *pdev)
 326{
 327        snd_soc_unregister_platform(&pdev->dev);
 328        return 0;
 329}
 330
 331static struct platform_driver ep93xx_pcm_driver = {
 332        .driver = {
 333                        .name = "ep93xx-pcm-audio",
 334                        .owner = THIS_MODULE,
 335        },
 336
 337        .probe = ep93xx_soc_platform_probe,
 338        .remove = __devexit_p(ep93xx_soc_platform_remove),
 339};
 340
 341module_platform_driver(ep93xx_pcm_driver);
 342
 343MODULE_AUTHOR("Ryan Mallon");
 344MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
 345MODULE_LICENSE("GPL");
 346MODULE_ALIAS("platform:ep93xx-pcm-audio");
 347
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.