linux/sound/soc/mxs/mxs-pcm.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
   3 *
   4 * Based on sound/soc/imx/imx-pcm-dma-mx2.c
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 * GNU General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU General Public License along
  17 * with this program; if not, write to the Free Software Foundation, Inc.,
  18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19 */
  20
  21#include <linux/clk.h>
  22#include <linux/delay.h>
  23#include <linux/device.h>
  24#include <linux/dma-mapping.h>
  25#include <linux/init.h>
  26#include <linux/interrupt.h>
  27#include <linux/module.h>
  28#include <linux/platform_device.h>
  29#include <linux/slab.h>
  30#include <linux/dmaengine.h>
  31
  32#include <sound/core.h>
  33#include <sound/initval.h>
  34#include <sound/pcm.h>
  35#include <sound/pcm_params.h>
  36#include <sound/soc.h>
  37
  38#include <mach/dma.h>
  39#include "mxs-pcm.h"
  40
  41static struct snd_pcm_hardware snd_mxs_hardware = {
  42        .info                   = SNDRV_PCM_INFO_MMAP |
  43                                  SNDRV_PCM_INFO_MMAP_VALID |
  44                                  SNDRV_PCM_INFO_PAUSE |
  45                                  SNDRV_PCM_INFO_RESUME |
  46                                  SNDRV_PCM_INFO_INTERLEAVED,
  47        .formats                = SNDRV_PCM_FMTBIT_S16_LE |
  48                                  SNDRV_PCM_FMTBIT_S20_3LE |
  49                                  SNDRV_PCM_FMTBIT_S24_LE,
  50        .channels_min           = 2,
  51        .channels_max           = 2,
  52        .period_bytes_min       = 32,
  53        .period_bytes_max       = 8192,
  54        .periods_min            = 1,
  55        .periods_max            = 52,
  56        .buffer_bytes_max       = 64 * 1024,
  57        .fifo_size              = 32,
  58
  59};
  60
  61static void audio_dma_irq(void *data)
  62{
  63        struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
  64        struct snd_pcm_runtime *runtime = substream->runtime;
  65        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
  66
  67        iprtd->offset += iprtd->period_bytes;
  68        iprtd->offset %= iprtd->period_bytes * iprtd->periods;
  69        snd_pcm_period_elapsed(substream);
  70}
  71
  72static bool filter(struct dma_chan *chan, void *param)
  73{
  74        struct mxs_pcm_runtime_data *iprtd = param;
  75        struct mxs_pcm_dma_params *dma_params = iprtd->dma_params;
  76
  77        if (!mxs_dma_is_apbx(chan))
  78                return false;
  79
  80        if (chan->chan_id != dma_params->chan_num)
  81                return false;
  82
  83        chan->private = &iprtd->dma_data;
  84
  85        return true;
  86}
  87
  88static int mxs_dma_alloc(struct snd_pcm_substream *substream,
  89                                struct snd_pcm_hw_params *params)
  90{
  91        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  92        struct snd_pcm_runtime *runtime = substream->runtime;
  93        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
  94        dma_cap_mask_t mask;
  95
  96        iprtd->dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
  97
  98        dma_cap_zero(mask);
  99        dma_cap_set(DMA_SLAVE, mask);
 100        iprtd->dma_data.chan_irq = iprtd->dma_params->chan_irq;
 101        iprtd->dma_chan = dma_request_channel(mask, filter, iprtd);
 102        if (!iprtd->dma_chan)
 103                return -EINVAL;
 104
 105        return 0;
 106}
 107
 108static int snd_mxs_pcm_hw_params(struct snd_pcm_substream *substream,
 109                                struct snd_pcm_hw_params *params)
 110{
 111        struct snd_pcm_runtime *runtime = substream->runtime;
 112        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
 113        unsigned long dma_addr;
 114        struct dma_chan *chan;
 115        int ret;
 116
 117        ret = mxs_dma_alloc(substream, params);
 118        if (ret)
 119                return ret;
 120        chan = iprtd->dma_chan;
 121
 122        iprtd->size = params_buffer_bytes(params);
 123        iprtd->periods = params_periods(params);
 124        iprtd->period_bytes = params_period_bytes(params);
 125        iprtd->offset = 0;
 126        iprtd->period_time = HZ / (params_rate(params) /
 127                        params_period_size(params));
 128
 129        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 130
 131        dma_addr = runtime->dma_addr;
 132
 133        iprtd->buf = substream->dma_buffer.area;
 134
 135        iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr,
 136                        iprtd->period_bytes * iprtd->periods,
 137                        iprtd->period_bytes,
 138                        substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
 139                        DMA_TO_DEVICE : DMA_FROM_DEVICE);
 140        if (!iprtd->desc) {
 141                dev_err(&chan->dev->device, "cannot prepare slave dma\n");
 142                return -EINVAL;
 143        }
 144
 145        iprtd->desc->callback = audio_dma_irq;
 146        iprtd->desc->callback_param = substream;
 147
 148        return 0;
 149}
 150
 151static int snd_mxs_pcm_hw_free(struct snd_pcm_substream *substream)
 152{
 153        struct snd_pcm_runtime *runtime = substream->runtime;
 154        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
 155
 156        if (iprtd->dma_chan) {
 157                dma_release_channel(iprtd->dma_chan);
 158                iprtd->dma_chan = NULL;
 159        }
 160
 161        return 0;
 162}
 163
 164static int snd_mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 165{
 166        struct snd_pcm_runtime *runtime = substream->runtime;
 167        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
 168
 169        switch (cmd) {
 170        case SNDRV_PCM_TRIGGER_START:
 171        case SNDRV_PCM_TRIGGER_RESUME:
 172        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 173                dmaengine_submit(iprtd->desc);
 174
 175                break;
 176        case SNDRV_PCM_TRIGGER_STOP:
 177        case SNDRV_PCM_TRIGGER_SUSPEND:
 178        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 179                dmaengine_terminate_all(iprtd->dma_chan);
 180
 181                break;
 182        default:
 183                return -EINVAL;
 184        }
 185
 186        return 0;
 187}
 188
 189static snd_pcm_uframes_t snd_mxs_pcm_pointer(
 190                struct snd_pcm_substream *substream)
 191{
 192        struct snd_pcm_runtime *runtime = substream->runtime;
 193        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
 194
 195        return bytes_to_frames(substream->runtime, iprtd->offset);
 196}
 197
 198static int snd_mxs_open(struct snd_pcm_substream *substream)
 199{
 200        struct snd_pcm_runtime *runtime = substream->runtime;
 201        struct mxs_pcm_runtime_data *iprtd;
 202        int ret;
 203
 204        iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
 205        if (iprtd == NULL)
 206                return -ENOMEM;
 207        runtime->private_data = iprtd;
 208
 209        ret = snd_pcm_hw_constraint_integer(substream->runtime,
 210                        SNDRV_PCM_HW_PARAM_PERIODS);
 211        if (ret < 0) {
 212                kfree(iprtd);
 213                return ret;
 214        }
 215
 216        snd_soc_set_runtime_hwparams(substream, &snd_mxs_hardware);
 217
 218        return 0;
 219}
 220
 221static int snd_mxs_close(struct snd_pcm_substream *substream)
 222{
 223        struct snd_pcm_runtime *runtime = substream->runtime;
 224        struct mxs_pcm_runtime_data *iprtd = runtime->private_data;
 225
 226        kfree(iprtd);
 227
 228        return 0;
 229}
 230
 231static int snd_mxs_pcm_mmap(struct snd_pcm_substream *substream,
 232                struct vm_area_struct *vma)
 233{
 234        struct snd_pcm_runtime *runtime = substream->runtime;
 235
 236        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 237                                        runtime->dma_area,
 238                                        runtime->dma_addr,
 239                                        runtime->dma_bytes);
 240}
 241
 242static struct snd_pcm_ops mxs_pcm_ops = {
 243        .open           = snd_mxs_open,
 244        .close          = snd_mxs_close,
 245        .ioctl          = snd_pcm_lib_ioctl,
 246        .hw_params      = snd_mxs_pcm_hw_params,
 247        .hw_free        = snd_mxs_pcm_hw_free,
 248        .trigger        = snd_mxs_pcm_trigger,
 249        .pointer        = snd_mxs_pcm_pointer,
 250        .mmap           = snd_mxs_pcm_mmap,
 251};
 252
 253static int mxs_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 254{
 255        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 256        struct snd_dma_buffer *buf = &substream->dma_buffer;
 257        size_t size = snd_mxs_hardware.buffer_bytes_max;
 258
 259        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 260        buf->dev.dev = pcm->card->dev;
 261        buf->private_data = NULL;
 262        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
 263                                           &buf->addr, GFP_KERNEL);
 264        if (!buf->area)
 265                return -ENOMEM;
 266        buf->bytes = size;
 267
 268        return 0;
 269}
 270
 271static u64 mxs_pcm_dmamask = DMA_BIT_MASK(32);
 272static int mxs_pcm_new(struct snd_soc_pcm_runtime *rtd)
 273{
 274        struct snd_card *card = rtd->card->snd_card;
 275        struct snd_pcm *pcm = rtd->pcm;
 276        int ret = 0;
 277
 278        if (!card->dev->dma_mask)
 279                card->dev->dma_mask = &mxs_pcm_dmamask;
 280        if (!card->dev->coherent_dma_mask)
 281                card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
 282
 283        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 284                ret = mxs_pcm_preallocate_dma_buffer(pcm,
 285                        SNDRV_PCM_STREAM_PLAYBACK);
 286                if (ret)
 287                        goto out;
 288        }
 289
 290        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 291                ret = mxs_pcm_preallocate_dma_buffer(pcm,
 292                        SNDRV_PCM_STREAM_CAPTURE);
 293                if (ret)
 294                        goto out;
 295        }
 296
 297out:
 298        return ret;
 299}
 300
 301static void mxs_pcm_free(struct snd_pcm *pcm)
 302{
 303        struct snd_pcm_substream *substream;
 304        struct snd_dma_buffer *buf;
 305        int stream;
 306
 307        for (stream = 0; stream < 2; stream++) {
 308                substream = pcm->streams[stream].substream;
 309                if (!substream)
 310                        continue;
 311
 312                buf = &substream->dma_buffer;
 313                if (!buf->area)
 314                        continue;
 315
 316                dma_free_writecombine(pcm->card->dev, buf->bytes,
 317                                      buf->area, buf->addr);
 318                buf->area = NULL;
 319        }
 320}
 321
 322static struct snd_soc_platform_driver mxs_soc_platform = {
 323        .ops            = &mxs_pcm_ops,
 324        .pcm_new        = mxs_pcm_new,
 325        .pcm_free       = mxs_pcm_free,
 326};
 327
 328static int __devinit mxs_soc_platform_probe(struct platform_device *pdev)
 329{
 330        return snd_soc_register_platform(&pdev->dev, &mxs_soc_platform);
 331}
 332
 333static int __devexit mxs_soc_platform_remove(struct platform_device *pdev)
 334{
 335        snd_soc_unregister_platform(&pdev->dev);
 336
 337        return 0;
 338}
 339
 340static struct platform_driver mxs_pcm_driver = {
 341        .driver = {
 342                .name = "mxs-pcm-audio",
 343                .owner = THIS_MODULE,
 344        },
 345        .probe = mxs_soc_platform_probe,
 346        .remove = __devexit_p(mxs_soc_platform_remove),
 347};
 348
 349static int __init snd_mxs_pcm_init(void)
 350{
 351        return platform_driver_register(&mxs_pcm_driver);
 352}
 353module_init(snd_mxs_pcm_init);
 354
 355static void __exit snd_mxs_pcm_exit(void)
 356{
 357        platform_driver_unregister(&mxs_pcm_driver);
 358}
 359module_exit(snd_mxs_pcm_exit);
 360
 361MODULE_LICENSE("GPL");
 362MODULE_ALIAS("platform:mxs-pcm-audio");
 363
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.