linux/sound/soc/tegra/tegra_pcm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * tegra_pcm.c - Tegra PCM driver
   4 *
   5 * Author: Stephen Warren <swarren@nvidia.com>
   6 * Copyright (C) 2010,2012 - NVIDIA, Inc.
   7 *
   8 * Based on code copyright/by:
   9 *
  10 * Copyright (c) 2009-2010, NVIDIA Corporation.
  11 * Scott Peterson <speterson@nvidia.com>
  12 * Vijay Mali <vmali@nvidia.com>
  13 *
  14 * Copyright (C) 2010 Google, Inc.
  15 * Iliyan Malchev <malchev@google.com>
  16 */
  17
  18#include <linux/module.h>
  19#include <linux/dma-mapping.h>
  20#include <sound/core.h>
  21#include <sound/pcm.h>
  22#include <sound/pcm_params.h>
  23#include <sound/soc.h>
  24#include <sound/dmaengine_pcm.h>
  25#include "tegra_pcm.h"
  26
  27static const struct snd_pcm_hardware tegra_pcm_hardware = {
  28        .info                   = SNDRV_PCM_INFO_MMAP |
  29                                  SNDRV_PCM_INFO_MMAP_VALID |
  30                                  SNDRV_PCM_INFO_INTERLEAVED,
  31        .period_bytes_min       = 1024,
  32        .period_bytes_max       = PAGE_SIZE,
  33        .periods_min            = 2,
  34        .periods_max            = 8,
  35        .buffer_bytes_max       = PAGE_SIZE * 8,
  36        .fifo_size              = 4,
  37};
  38
  39static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = {
  40        .pcm_hardware = &tegra_pcm_hardware,
  41        .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
  42        .prealloc_buffer_size = PAGE_SIZE * 8,
  43};
  44
  45int tegra_pcm_platform_register(struct device *dev)
  46{
  47        return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0);
  48}
  49EXPORT_SYMBOL_GPL(tegra_pcm_platform_register);
  50
  51int tegra_pcm_platform_register_with_chan_names(struct device *dev,
  52                                struct snd_dmaengine_pcm_config *config,
  53                                char *txdmachan, char *rxdmachan)
  54{
  55        *config = tegra_dmaengine_pcm_config;
  56        config->dma_dev = dev->parent;
  57        config->chan_names[0] = txdmachan;
  58        config->chan_names[1] = rxdmachan;
  59
  60        return snd_dmaengine_pcm_register(dev, config, 0);
  61}
  62EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names);
  63
  64void tegra_pcm_platform_unregister(struct device *dev)
  65{
  66        return snd_dmaengine_pcm_unregister(dev);
  67}
  68EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister);
  69
  70int tegra_pcm_open(struct snd_soc_component *component,
  71                   struct snd_pcm_substream *substream)
  72{
  73        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  74        struct snd_dmaengine_dai_dma_data *dmap;
  75        struct dma_chan *chan;
  76        struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
  77        int ret;
  78
  79        if (rtd->dai_link->no_pcm)
  80                return 0;
  81
  82        dmap = snd_soc_dai_get_dma_data(cpu_dai, substream);
  83
  84        /* Set HW params now that initialization is complete */
  85        snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware);
  86
  87        /* Ensure period size is multiple of 8 */
  88        ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
  89                                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8);
  90        if (ret) {
  91                dev_err(rtd->dev, "failed to set constraint %d\n", ret);
  92                return ret;
  93        }
  94
  95        chan = dma_request_slave_channel(cpu_dai->dev, dmap->chan_name);
  96        if (!chan) {
  97                dev_err(cpu_dai->dev,
  98                        "dmaengine request slave channel failed! (%s)\n",
  99                        dmap->chan_name);
 100                return -ENODEV;
 101        }
 102
 103        ret = snd_dmaengine_pcm_open(substream, chan);
 104        if (ret) {
 105                dev_err(rtd->dev,
 106                        "dmaengine pcm open failed with err %d (%s)\n", ret,
 107                        dmap->chan_name);
 108
 109                dma_release_channel(chan);
 110
 111                return ret;
 112        }
 113
 114        return 0;
 115}
 116EXPORT_SYMBOL_GPL(tegra_pcm_open);
 117
 118int tegra_pcm_close(struct snd_soc_component *component,
 119                    struct snd_pcm_substream *substream)
 120{
 121        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 122
 123        if (rtd->dai_link->no_pcm)
 124                return 0;
 125
 126        snd_dmaengine_pcm_close_release_chan(substream);
 127
 128        return 0;
 129}
 130EXPORT_SYMBOL_GPL(tegra_pcm_close);
 131
 132int tegra_pcm_hw_params(struct snd_soc_component *component,
 133                        struct snd_pcm_substream *substream,
 134                        struct snd_pcm_hw_params *params)
 135{
 136        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 137        struct snd_dmaengine_dai_dma_data *dmap;
 138        struct dma_slave_config slave_config;
 139        struct dma_chan *chan;
 140        int ret;
 141
 142        if (rtd->dai_link->no_pcm)
 143                return 0;
 144
 145        dmap = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
 146        if (!dmap)
 147                return 0;
 148
 149        chan = snd_dmaengine_pcm_get_chan(substream);
 150
 151        ret = snd_hwparams_to_dma_slave_config(substream, params,
 152                                               &slave_config);
 153        if (ret) {
 154                dev_err(rtd->dev, "hw params config failed with err %d\n", ret);
 155                return ret;
 156        }
 157
 158        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 159                slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 160                slave_config.dst_addr = dmap->addr;
 161                slave_config.dst_maxburst = 8;
 162        } else {
 163                slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 164                slave_config.src_addr = dmap->addr;
 165                slave_config.src_maxburst = 8;
 166        }
 167
 168        ret = dmaengine_slave_config(chan, &slave_config);
 169        if (ret < 0) {
 170                dev_err(rtd->dev, "dma slave config failed with err %d\n", ret);
 171                return ret;
 172        }
 173
 174        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 175
 176        return 0;
 177}
 178EXPORT_SYMBOL_GPL(tegra_pcm_hw_params);
 179
 180int tegra_pcm_hw_free(struct snd_soc_component *component,
 181                      struct snd_pcm_substream *substream)
 182{
 183        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 184
 185        if (rtd->dai_link->no_pcm)
 186                return 0;
 187
 188        snd_pcm_set_runtime_buffer(substream, NULL);
 189
 190        return 0;
 191}
 192EXPORT_SYMBOL_GPL(tegra_pcm_hw_free);
 193
 194int tegra_pcm_mmap(struct snd_soc_component *component,
 195                   struct snd_pcm_substream *substream,
 196                   struct vm_area_struct *vma)
 197{
 198        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 199        struct snd_pcm_runtime *runtime = substream->runtime;
 200
 201        if (rtd->dai_link->no_pcm)
 202                return 0;
 203
 204        return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area,
 205                           runtime->dma_addr, runtime->dma_bytes);
 206}
 207EXPORT_SYMBOL_GPL(tegra_pcm_mmap);
 208
 209snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component,
 210                                    struct snd_pcm_substream *substream)
 211{
 212        return snd_dmaengine_pcm_pointer(substream);
 213}
 214EXPORT_SYMBOL_GPL(tegra_pcm_pointer);
 215
 216static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream,
 217                                            size_t size)
 218{
 219        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 220        struct snd_dma_buffer *buf = &substream->dma_buffer;
 221
 222        buf->area = dma_alloc_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL);
 223        if (!buf->area)
 224                return -ENOMEM;
 225
 226        buf->private_data = NULL;
 227        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 228        buf->dev.dev = pcm->card->dev;
 229        buf->bytes = size;
 230
 231        return 0;
 232}
 233
 234static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 235{
 236        struct snd_pcm_substream *substream;
 237        struct snd_dma_buffer *buf;
 238
 239        substream = pcm->streams[stream].substream;
 240        if (!substream)
 241                return;
 242
 243        buf = &substream->dma_buffer;
 244        if (!buf->area)
 245                return;
 246
 247        dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr);
 248        buf->area = NULL;
 249}
 250
 251static int tegra_pcm_dma_allocate(struct snd_soc_pcm_runtime *rtd,
 252                                  size_t size)
 253{
 254        struct snd_card *card = rtd->card->snd_card;
 255        struct snd_pcm *pcm = rtd->pcm;
 256        int ret;
 257
 258        ret = dma_set_mask(card->dev, DMA_BIT_MASK(32));
 259        if (ret < 0)
 260                return ret;
 261
 262        ret = dma_set_coherent_mask(card->dev, DMA_BIT_MASK(32));
 263        if (ret < 0)
 264                return ret;
 265
 266        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 267                ret = tegra_pcm_preallocate_dma_buffer(pcm,
 268                        SNDRV_PCM_STREAM_PLAYBACK, size);
 269                if (ret)
 270                        goto err;
 271        }
 272
 273        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 274                ret = tegra_pcm_preallocate_dma_buffer(pcm,
 275                        SNDRV_PCM_STREAM_CAPTURE, size);
 276                if (ret)
 277                        goto err_free_play;
 278        }
 279
 280        return 0;
 281
 282err_free_play:
 283        tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
 284err:
 285        return ret;
 286}
 287
 288int tegra_pcm_construct(struct snd_soc_component *component,
 289                        struct snd_soc_pcm_runtime *rtd)
 290{
 291        return tegra_pcm_dma_allocate(rtd, tegra_pcm_hardware.buffer_bytes_max);
 292}
 293EXPORT_SYMBOL_GPL(tegra_pcm_construct);
 294
 295void tegra_pcm_destruct(struct snd_soc_component *component,
 296                        struct snd_pcm *pcm)
 297{
 298        tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
 299        tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
 300}
 301EXPORT_SYMBOL_GPL(tegra_pcm_destruct);
 302
 303MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
 304MODULE_DESCRIPTION("Tegra PCM ASoC driver");
 305MODULE_LICENSE("GPL");
 306