linux/sound/soc/au1x/dma.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Au1000/Au1500/Au1100 Audio DMA support.
   4 *
   5 * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
   6 *
   7 * copied almost verbatim from the old ALSA driver, written by
   8 *                      Charles Eidsness <charles@cooper-street.com>
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/init.h>
  13#include <linux/platform_device.h>
  14#include <linux/slab.h>
  15#include <linux/dma-mapping.h>
  16#include <sound/core.h>
  17#include <sound/pcm.h>
  18#include <sound/pcm_params.h>
  19#include <sound/soc.h>
  20#include <asm/mach-au1x00/au1000.h>
  21#include <asm/mach-au1x00/au1000_dma.h>
  22
  23#include "psc.h"
  24
  25#define DRV_NAME "au1x_dma"
  26
  27struct pcm_period {
  28        u32 start;
  29        u32 relative_end;       /* relative to start of buffer */
  30        struct pcm_period *next;
  31};
  32
  33struct audio_stream {
  34        struct snd_pcm_substream *substream;
  35        int dma;
  36        struct pcm_period *buffer;
  37        unsigned int period_size;
  38        unsigned int periods;
  39};
  40
  41struct alchemy_pcm_ctx {
  42        struct audio_stream stream[2];  /* playback & capture */
  43};
  44
  45static void au1000_release_dma_link(struct audio_stream *stream)
  46{
  47        struct pcm_period *pointer;
  48        struct pcm_period *pointer_next;
  49
  50        stream->period_size = 0;
  51        stream->periods = 0;
  52        pointer = stream->buffer;
  53        if (!pointer)
  54                return;
  55        do {
  56                pointer_next = pointer->next;
  57                kfree(pointer);
  58                pointer = pointer_next;
  59        } while (pointer != stream->buffer);
  60        stream->buffer = NULL;
  61}
  62
  63static int au1000_setup_dma_link(struct audio_stream *stream,
  64                                 unsigned int period_bytes,
  65                                 unsigned int periods)
  66{
  67        struct snd_pcm_substream *substream = stream->substream;
  68        struct snd_pcm_runtime *runtime = substream->runtime;
  69        struct pcm_period *pointer;
  70        unsigned long dma_start;
  71        int i;
  72
  73        dma_start = virt_to_phys(runtime->dma_area);
  74
  75        if (stream->period_size == period_bytes &&
  76            stream->periods == periods)
  77                return 0; /* not changed */
  78
  79        au1000_release_dma_link(stream);
  80
  81        stream->period_size = period_bytes;
  82        stream->periods = periods;
  83
  84        stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
  85        if (!stream->buffer)
  86                return -ENOMEM;
  87        pointer = stream->buffer;
  88        for (i = 0; i < periods; i++) {
  89                pointer->start = (u32)(dma_start + (i * period_bytes));
  90                pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
  91                if (i < periods - 1) {
  92                        pointer->next = kmalloc(sizeof(struct pcm_period),
  93                                                GFP_KERNEL);
  94                        if (!pointer->next) {
  95                                au1000_release_dma_link(stream);
  96                                return -ENOMEM;
  97                        }
  98                        pointer = pointer->next;
  99                }
 100        }
 101        pointer->next = stream->buffer;
 102        return 0;
 103}
 104
 105static void au1000_dma_stop(struct audio_stream *stream)
 106{
 107        if (stream->buffer)
 108                disable_dma(stream->dma);
 109}
 110
 111static void au1000_dma_start(struct audio_stream *stream)
 112{
 113        if (!stream->buffer)
 114                return;
 115
 116        init_dma(stream->dma);
 117        if (get_dma_active_buffer(stream->dma) == 0) {
 118                clear_dma_done0(stream->dma);
 119                set_dma_addr0(stream->dma, stream->buffer->start);
 120                set_dma_count0(stream->dma, stream->period_size >> 1);
 121                set_dma_addr1(stream->dma, stream->buffer->next->start);
 122                set_dma_count1(stream->dma, stream->period_size >> 1);
 123        } else {
 124                clear_dma_done1(stream->dma);
 125                set_dma_addr1(stream->dma, stream->buffer->start);
 126                set_dma_count1(stream->dma, stream->period_size >> 1);
 127                set_dma_addr0(stream->dma, stream->buffer->next->start);
 128                set_dma_count0(stream->dma, stream->period_size >> 1);
 129        }
 130        enable_dma_buffers(stream->dma);
 131        start_dma(stream->dma);
 132}
 133
 134static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
 135{
 136        struct audio_stream *stream = (struct audio_stream *)ptr;
 137        struct snd_pcm_substream *substream = stream->substream;
 138
 139        switch (get_dma_buffer_done(stream->dma)) {
 140        case DMA_D0:
 141                stream->buffer = stream->buffer->next;
 142                clear_dma_done0(stream->dma);
 143                set_dma_addr0(stream->dma, stream->buffer->next->start);
 144                set_dma_count0(stream->dma, stream->period_size >> 1);
 145                enable_dma_buffer0(stream->dma);
 146                break;
 147        case DMA_D1:
 148                stream->buffer = stream->buffer->next;
 149                clear_dma_done1(stream->dma);
 150                set_dma_addr1(stream->dma, stream->buffer->next->start);
 151                set_dma_count1(stream->dma, stream->period_size >> 1);
 152                enable_dma_buffer1(stream->dma);
 153                break;
 154        case (DMA_D0 | DMA_D1):
 155                pr_debug("DMA %d missed interrupt.\n", stream->dma);
 156                au1000_dma_stop(stream);
 157                au1000_dma_start(stream);
 158                break;
 159        case (~DMA_D0 & ~DMA_D1):
 160                pr_debug("DMA %d empty irq.\n", stream->dma);
 161        }
 162        snd_pcm_period_elapsed(substream);
 163        return IRQ_HANDLED;
 164}
 165
 166static const struct snd_pcm_hardware alchemy_pcm_hardware = {
 167        .info             = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
 168                            SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
 169        .period_bytes_min = 1024,
 170        .period_bytes_max = 16 * 1024 - 1,
 171        .periods_min      = 4,
 172        .periods_max      = 255,
 173        .buffer_bytes_max = 128 * 1024,
 174        .fifo_size        = 16,
 175};
 176
 177static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss,
 178                                                struct snd_soc_component *component)
 179{
 180        return snd_soc_component_get_drvdata(component);
 181}
 182
 183static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss,
 184                                            struct snd_soc_component *component)
 185{
 186        struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss, component);
 187        return &(ctx->stream[ss->stream]);
 188}
 189
 190static int alchemy_pcm_open(struct snd_soc_component *component,
 191                            struct snd_pcm_substream *substream)
 192{
 193        struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream, component);
 194        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 195        int *dmaids, s = substream->stream;
 196        char *name;
 197
 198        dmaids = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
 199        if (!dmaids)
 200                return -ENODEV; /* whoa, has ordering changed? */
 201
 202        /* DMA setup */
 203        name = (s == SNDRV_PCM_STREAM_PLAYBACK) ? "audio-tx" : "audio-rx";
 204        ctx->stream[s].dma = request_au1000_dma(dmaids[s], name,
 205                                        au1000_dma_interrupt, 0,
 206                                        &ctx->stream[s]);
 207        set_dma_mode(ctx->stream[s].dma,
 208                     get_dma_mode(ctx->stream[s].dma) & ~DMA_NC);
 209
 210        ctx->stream[s].substream = substream;
 211        ctx->stream[s].buffer = NULL;
 212        snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
 213
 214        return 0;
 215}
 216
 217static int alchemy_pcm_close(struct snd_soc_component *component,
 218                             struct snd_pcm_substream *substream)
 219{
 220        struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream, component);
 221        int stype = substream->stream;
 222
 223        ctx->stream[stype].substream = NULL;
 224        free_au1000_dma(ctx->stream[stype].dma);
 225
 226        return 0;
 227}
 228
 229static int alchemy_pcm_hw_params(struct snd_soc_component *component,
 230                                 struct snd_pcm_substream *substream,
 231                                 struct snd_pcm_hw_params *hw_params)
 232{
 233        struct audio_stream *stream = ss_to_as(substream, component);
 234
 235        return au1000_setup_dma_link(stream,
 236                                     params_period_bytes(hw_params),
 237                                     params_periods(hw_params));
 238}
 239
 240static int alchemy_pcm_hw_free(struct snd_soc_component *component,
 241                               struct snd_pcm_substream *substream)
 242{
 243        struct audio_stream *stream = ss_to_as(substream, component);
 244        au1000_release_dma_link(stream);
 245        return 0;
 246}
 247
 248static int alchemy_pcm_trigger(struct snd_soc_component *component,
 249                               struct snd_pcm_substream *substream, int cmd)
 250{
 251        struct audio_stream *stream = ss_to_as(substream, component);
 252        int err = 0;
 253
 254        switch (cmd) {
 255        case SNDRV_PCM_TRIGGER_START:
 256                au1000_dma_start(stream);
 257                break;
 258        case SNDRV_PCM_TRIGGER_STOP:
 259                au1000_dma_stop(stream);
 260                break;
 261        default:
 262                err = -EINVAL;
 263                break;
 264        }
 265        return err;
 266}
 267
 268static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_soc_component *component,
 269                                             struct snd_pcm_substream *ss)
 270{
 271        struct audio_stream *stream = ss_to_as(ss, component);
 272        long location;
 273
 274        location = get_dma_residue(stream->dma);
 275        location = stream->buffer->relative_end - location;
 276        if (location == -1)
 277                location = 0;
 278        return bytes_to_frames(ss->runtime, location);
 279}
 280
 281static int alchemy_pcm_new(struct snd_soc_component *component,
 282                           struct snd_soc_pcm_runtime *rtd)
 283{
 284        struct snd_pcm *pcm = rtd->pcm;
 285
 286        snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
 287                                       NULL, 65536, (4096 * 1024) - 1);
 288
 289        return 0;
 290}
 291
 292static struct snd_soc_component_driver alchemy_pcm_soc_component = {
 293        .name           = DRV_NAME,
 294        .open           = alchemy_pcm_open,
 295        .close          = alchemy_pcm_close,
 296        .hw_params      = alchemy_pcm_hw_params,
 297        .hw_free        = alchemy_pcm_hw_free,
 298        .trigger        = alchemy_pcm_trigger,
 299        .pointer        = alchemy_pcm_pointer,
 300        .pcm_construct  = alchemy_pcm_new,
 301};
 302
 303static int alchemy_pcm_drvprobe(struct platform_device *pdev)
 304{
 305        struct alchemy_pcm_ctx *ctx;
 306
 307        ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
 308        if (!ctx)
 309                return -ENOMEM;
 310
 311        platform_set_drvdata(pdev, ctx);
 312
 313        return devm_snd_soc_register_component(&pdev->dev,
 314                                        &alchemy_pcm_soc_component, NULL, 0);
 315}
 316
 317static struct platform_driver alchemy_pcmdma_driver = {
 318        .driver = {
 319                .name   = "alchemy-pcm-dma",
 320        },
 321        .probe          = alchemy_pcm_drvprobe,
 322};
 323
 324module_platform_driver(alchemy_pcmdma_driver);
 325
 326MODULE_LICENSE("GPL");
 327MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
 328MODULE_AUTHOR("Manuel Lauss");
 329