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 <jarkko.nikula@bitmer.com>
   7 *          Peter Ujfalusi <peter.ujfalusi@ti.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 <linux/module.h>
  28#include <linux/omap-dma.h>
  29#include <sound/core.h>
  30#include <sound/pcm.h>
  31#include <sound/pcm_params.h>
  32#include <sound/dmaengine_pcm.h>
  33#include <sound/soc.h>
  34
  35#include "omap-pcm.h"
  36
  37#ifdef CONFIG_ARCH_OMAP1
  38#define pcm_omap1510()  cpu_is_omap1510()
  39#else
  40#define pcm_omap1510()  0
  41#endif
  42
  43static const struct snd_pcm_hardware omap_pcm_hardware = {
  44        .info                   = SNDRV_PCM_INFO_MMAP |
  45                                  SNDRV_PCM_INFO_MMAP_VALID |
  46                                  SNDRV_PCM_INFO_INTERLEAVED |
  47                                  SNDRV_PCM_INFO_PAUSE |
  48                                  SNDRV_PCM_INFO_RESUME |
  49                                  SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
  50        .formats                = SNDRV_PCM_FMTBIT_S16_LE |
  51                                  SNDRV_PCM_FMTBIT_S32_LE,
  52        .period_bytes_min       = 32,
  53        .period_bytes_max       = 64 * 1024,
  54        .periods_min            = 2,
  55        .periods_max            = 255,
  56        .buffer_bytes_max       = 128 * 1024,
  57};
  58
  59static int omap_pcm_get_dma_buswidth(int num_bits)
  60{
  61        int buswidth;
  62
  63        switch (num_bits) {
  64        case 16:
  65                buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
  66                break;
  67        case 32:
  68                buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
  69                break;
  70        default:
  71                buswidth = -EINVAL;
  72                break;
  73        }
  74        return buswidth;
  75}
  76
  77
  78/* this may get called several times by oss emulation */
  79static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
  80                              struct snd_pcm_hw_params *params)
  81{
  82        struct snd_pcm_runtime *runtime = substream->runtime;
  83        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  84        struct omap_pcm_dma_data *dma_data;
  85        struct dma_slave_config config;
  86        struct dma_chan *chan;
  87        int err = 0;
  88
  89        dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
  90
  91        /* return if this is a bufferless transfer e.g.
  92         * codec <--> BT codec or GSM modem -- lg FIXME */
  93        if (!dma_data)
  94                return 0;
  95
  96        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
  97        runtime->dma_bytes = params_buffer_bytes(params);
  98
  99        chan = snd_dmaengine_pcm_get_chan(substream);
 100        if (!chan)
 101                return -EINVAL;
 102
 103        /* fills in addr_width and direction */
 104        err = snd_hwparams_to_dma_slave_config(substream, params, &config);
 105        if (err)
 106                return err;
 107
 108        /* Override the *_dma addr_width if requested by the DAI driver */
 109        if (dma_data->data_type) {
 110                int buswidth = omap_pcm_get_dma_buswidth(dma_data->data_type);
 111
 112                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 113                        config.dst_addr_width = buswidth;
 114                else
 115                        config.src_addr_width = buswidth;
 116        }
 117
 118        config.src_addr = dma_data->port_addr;
 119        config.dst_addr = dma_data->port_addr;
 120        config.src_maxburst = dma_data->packet_size;
 121        config.dst_maxburst = dma_data->packet_size;
 122
 123        return dmaengine_slave_config(chan, &config);
 124}
 125
 126static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
 127{
 128        snd_pcm_set_runtime_buffer(substream, NULL);
 129        return 0;
 130}
 131
 132static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 133{
 134        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 135        struct omap_pcm_dma_data *dma_data;
 136        int ret = 0;
 137
 138        dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
 139
 140        switch (cmd) {
 141        case SNDRV_PCM_TRIGGER_START:
 142        case SNDRV_PCM_TRIGGER_RESUME:
 143        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 144                /* Configure McBSP internal buffer usage */
 145                if (dma_data->set_threshold)
 146                        dma_data->set_threshold(substream);
 147                break;
 148
 149        case SNDRV_PCM_TRIGGER_STOP:
 150        case SNDRV_PCM_TRIGGER_SUSPEND:
 151        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 152                break;
 153        default:
 154                ret = -EINVAL;
 155        }
 156
 157        if (ret == 0)
 158                ret = snd_dmaengine_pcm_trigger(substream, cmd);
 159
 160        return ret;
 161}
 162
 163static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
 164{
 165        snd_pcm_uframes_t offset;
 166
 167        if (pcm_omap1510())
 168                offset = snd_dmaengine_pcm_pointer_no_residue(substream);
 169        else
 170                offset = snd_dmaengine_pcm_pointer(substream);
 171
 172        return offset;
 173}
 174
 175static int omap_pcm_open(struct snd_pcm_substream *substream)
 176{
 177        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 178        struct omap_pcm_dma_data *dma_data;
 179
 180        snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
 181
 182        dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
 183
 184        return snd_dmaengine_pcm_open(substream, omap_dma_filter_fn,
 185                                      &dma_data->dma_req);
 186}
 187
 188static int omap_pcm_close(struct snd_pcm_substream *substream)
 189{
 190        snd_dmaengine_pcm_close(substream);
 191        return 0;
 192}
 193
 194static int omap_pcm_mmap(struct snd_pcm_substream *substream,
 195        struct vm_area_struct *vma)
 196{
 197        struct snd_pcm_runtime *runtime = substream->runtime;
 198
 199        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 200                                     runtime->dma_area,
 201                                     runtime->dma_addr,
 202                                     runtime->dma_bytes);
 203}
 204
 205static struct snd_pcm_ops omap_pcm_ops = {
 206        .open           = omap_pcm_open,
 207        .close          = omap_pcm_close,
 208        .ioctl          = snd_pcm_lib_ioctl,
 209        .hw_params      = omap_pcm_hw_params,
 210        .hw_free        = omap_pcm_hw_free,
 211        .trigger        = omap_pcm_trigger,
 212        .pointer        = omap_pcm_pointer,
 213        .mmap           = omap_pcm_mmap,
 214};
 215
 216static u64 omap_pcm_dmamask = DMA_BIT_MASK(64);
 217
 218static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
 219        int stream)
 220{
 221        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 222        struct snd_dma_buffer *buf = &substream->dma_buffer;
 223        size_t size = omap_pcm_hardware.buffer_bytes_max;
 224
 225        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 226        buf->dev.dev = pcm->card->dev;
 227        buf->private_data = NULL;
 228        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
 229                                           &buf->addr, GFP_KERNEL);
 230        if (!buf->area)
 231                return -ENOMEM;
 232
 233        buf->bytes = size;
 234        return 0;
 235}
 236
 237static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
 238{
 239        struct snd_pcm_substream *substream;
 240        struct snd_dma_buffer *buf;
 241        int stream;
 242
 243        for (stream = 0; stream < 2; stream++) {
 244                substream = pcm->streams[stream].substream;
 245                if (!substream)
 246                        continue;
 247
 248                buf = &substream->dma_buffer;
 249                if (!buf->area)
 250                        continue;
 251
 252                dma_free_writecombine(pcm->card->dev, buf->bytes,
 253                                      buf->area, buf->addr);
 254                buf->area = NULL;
 255        }
 256}
 257
 258static int omap_pcm_new(struct snd_soc_pcm_runtime *rtd)
 259{
 260        struct snd_card *card = rtd->card->snd_card;
 261        struct snd_pcm *pcm = rtd->pcm;
 262        int ret = 0;
 263
 264        if (!card->dev->dma_mask)
 265                card->dev->dma_mask = &omap_pcm_dmamask;
 266        if (!card->dev->coherent_dma_mask)
 267                card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
 268
 269        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 270                ret = omap_pcm_preallocate_dma_buffer(pcm,
 271                        SNDRV_PCM_STREAM_PLAYBACK);
 272                if (ret)
 273                        goto out;
 274        }
 275
 276        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 277                ret = omap_pcm_preallocate_dma_buffer(pcm,
 278                        SNDRV_PCM_STREAM_CAPTURE);
 279                if (ret)
 280                        goto out;
 281        }
 282
 283out:
 284        /* free preallocated buffers in case of error */
 285        if (ret)
 286                omap_pcm_free_dma_buffers(pcm);
 287
 288        return ret;
 289}
 290
 291static struct snd_soc_platform_driver omap_soc_platform = {
 292        .ops            = &omap_pcm_ops,
 293        .pcm_new        = omap_pcm_new,
 294        .pcm_free       = omap_pcm_free_dma_buffers,
 295};
 296
 297static int omap_pcm_probe(struct platform_device *pdev)
 298{
 299        return snd_soc_register_platform(&pdev->dev,
 300                        &omap_soc_platform);
 301}
 302
 303static int omap_pcm_remove(struct platform_device *pdev)
 304{
 305        snd_soc_unregister_platform(&pdev->dev);
 306        return 0;
 307}
 308
 309static struct platform_driver omap_pcm_driver = {
 310        .driver = {
 311                        .name = "omap-pcm-audio",
 312                        .owner = THIS_MODULE,
 313        },
 314
 315        .probe = omap_pcm_probe,
 316        .remove = omap_pcm_remove,
 317};
 318
 319module_platform_driver(omap_pcm_driver);
 320
 321MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
 322MODULE_DESCRIPTION("OMAP PCM DMA module");
 323MODULE_LICENSE("GPL");
 324MODULE_ALIAS("platform:omap-pcm-audio");
 325
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.