linux/sound/soc/atmel/atmel-pcm-pdc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * atmel-pcm.c  --  ALSA PCM interface for the Atmel atmel SoC.
   4 *
   5 *  Copyright (C) 2005 SAN People
   6 *  Copyright (C) 2008 Atmel
   7 *
   8 * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
   9 *
  10 * Based on at91-pcm. by:
  11 * Frank Mandarino <fmandarino@endrelia.com>
  12 * Copyright 2006 Endrelia Technologies Inc.
  13 *
  14 * Based on pxa2xx-pcm.c by:
  15 *
  16 * Author:      Nicolas Pitre
  17 * Created:     Nov 30, 2004
  18 * Copyright:   (C) 2004 MontaVista Software, Inc.
  19 */
  20
  21#include <linux/module.h>
  22#include <linux/init.h>
  23#include <linux/platform_device.h>
  24#include <linux/slab.h>
  25#include <linux/dma-mapping.h>
  26#include <linux/atmel_pdc.h>
  27#include <linux/atmel-ssc.h>
  28
  29#include <sound/core.h>
  30#include <sound/pcm.h>
  31#include <sound/pcm_params.h>
  32#include <sound/soc.h>
  33
  34#include "atmel-pcm.h"
  35
  36
  37static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
  38        int stream)
  39{
  40        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
  41        struct snd_dma_buffer *buf = &substream->dma_buffer;
  42        size_t size = ATMEL_SSC_DMABUF_SIZE;
  43
  44        buf->dev.type = SNDRV_DMA_TYPE_DEV;
  45        buf->dev.dev = pcm->card->dev;
  46        buf->private_data = NULL;
  47        buf->area = dma_alloc_coherent(pcm->card->dev, size,
  48                        &buf->addr, GFP_KERNEL);
  49        pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n",
  50                        (void *)buf->area, (void *)(long)buf->addr, size);
  51
  52        if (!buf->area)
  53                return -ENOMEM;
  54
  55        buf->bytes = size;
  56        return 0;
  57}
  58
  59static int atmel_pcm_mmap(struct snd_soc_component *component,
  60                          struct snd_pcm_substream *substream,
  61                          struct vm_area_struct *vma)
  62{
  63        return remap_pfn_range(vma, vma->vm_start,
  64                       substream->dma_buffer.addr >> PAGE_SHIFT,
  65                       vma->vm_end - vma->vm_start, vma->vm_page_prot);
  66}
  67
  68static int atmel_pcm_new(struct snd_soc_component *component,
  69                         struct snd_soc_pcm_runtime *rtd)
  70{
  71        struct snd_card *card = rtd->card->snd_card;
  72        struct snd_pcm *pcm = rtd->pcm;
  73        int ret;
  74
  75        ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
  76        if (ret)
  77                return ret;
  78
  79        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
  80                pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n");
  81                ret = atmel_pcm_preallocate_dma_buffer(pcm,
  82                        SNDRV_PCM_STREAM_PLAYBACK);
  83                if (ret)
  84                        goto out;
  85        }
  86
  87        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
  88                pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
  89                ret = atmel_pcm_preallocate_dma_buffer(pcm,
  90                        SNDRV_PCM_STREAM_CAPTURE);
  91                if (ret)
  92                        goto out;
  93        }
  94 out:
  95        return ret;
  96}
  97
  98static void atmel_pcm_free(struct snd_soc_component *component,
  99                           struct snd_pcm *pcm)
 100{
 101        struct snd_pcm_substream *substream;
 102        struct snd_dma_buffer *buf;
 103        int stream;
 104
 105        for (stream = 0; stream < 2; stream++) {
 106                substream = pcm->streams[stream].substream;
 107                if (!substream)
 108                        continue;
 109
 110                buf = &substream->dma_buffer;
 111                if (!buf->area)
 112                        continue;
 113                dma_free_coherent(pcm->card->dev, buf->bytes,
 114                                  buf->area, buf->addr);
 115                buf->area = NULL;
 116        }
 117}
 118
 119/*--------------------------------------------------------------------------*\
 120 * Hardware definition
 121\*--------------------------------------------------------------------------*/
 122/* TODO: These values were taken from the AT91 platform driver, check
 123 *       them against real values for AT32
 124 */
 125static const struct snd_pcm_hardware atmel_pcm_hardware = {
 126        .info                   = SNDRV_PCM_INFO_MMAP |
 127                                  SNDRV_PCM_INFO_MMAP_VALID |
 128                                  SNDRV_PCM_INFO_INTERLEAVED |
 129                                  SNDRV_PCM_INFO_PAUSE,
 130        .period_bytes_min       = 32,
 131        .period_bytes_max       = 8192,
 132        .periods_min            = 2,
 133        .periods_max            = 1024,
 134        .buffer_bytes_max       = ATMEL_SSC_DMABUF_SIZE,
 135};
 136
 137
 138/*--------------------------------------------------------------------------*\
 139 * Data types
 140\*--------------------------------------------------------------------------*/
 141struct atmel_runtime_data {
 142        struct atmel_pcm_dma_params *params;
 143        dma_addr_t dma_buffer;          /* physical address of dma buffer */
 144        dma_addr_t dma_buffer_end;      /* first address beyond DMA buffer */
 145        size_t period_size;
 146
 147        dma_addr_t period_ptr;          /* physical address of next period */
 148};
 149
 150/*--------------------------------------------------------------------------*\
 151 * ISR
 152\*--------------------------------------------------------------------------*/
 153static void atmel_pcm_dma_irq(u32 ssc_sr,
 154        struct snd_pcm_substream *substream)
 155{
 156        struct atmel_runtime_data *prtd = substream->runtime->private_data;
 157        struct atmel_pcm_dma_params *params = prtd->params;
 158        static int count;
 159
 160        count++;
 161
 162        if (ssc_sr & params->mask->ssc_endbuf) {
 163                pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
 164                                substream->stream == SNDRV_PCM_STREAM_PLAYBACK
 165                                ? "underrun" : "overrun",
 166                                params->name, ssc_sr, count);
 167
 168                /* re-start the PDC */
 169                ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
 170                           params->mask->pdc_disable);
 171                prtd->period_ptr += prtd->period_size;
 172                if (prtd->period_ptr >= prtd->dma_buffer_end)
 173                        prtd->period_ptr = prtd->dma_buffer;
 174
 175                ssc_writex(params->ssc->regs, params->pdc->xpr,
 176                           prtd->period_ptr);
 177                ssc_writex(params->ssc->regs, params->pdc->xcr,
 178                           prtd->period_size / params->pdc_xfer_size);
 179                ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
 180                           params->mask->pdc_enable);
 181        }
 182
 183        if (ssc_sr & params->mask->ssc_endx) {
 184                /* Load the PDC next pointer and counter registers */
 185                prtd->period_ptr += prtd->period_size;
 186                if (prtd->period_ptr >= prtd->dma_buffer_end)
 187                        prtd->period_ptr = prtd->dma_buffer;
 188
 189                ssc_writex(params->ssc->regs, params->pdc->xnpr,
 190                           prtd->period_ptr);
 191                ssc_writex(params->ssc->regs, params->pdc->xncr,
 192                           prtd->period_size / params->pdc_xfer_size);
 193        }
 194
 195        snd_pcm_period_elapsed(substream);
 196}
 197
 198
 199/*--------------------------------------------------------------------------*\
 200 * PCM operations
 201\*--------------------------------------------------------------------------*/
 202static int atmel_pcm_hw_params(struct snd_soc_component *component,
 203                               struct snd_pcm_substream *substream,
 204                               struct snd_pcm_hw_params *params)
 205{
 206        struct snd_pcm_runtime *runtime = substream->runtime;
 207        struct atmel_runtime_data *prtd = runtime->private_data;
 208        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 209
 210        /* this may get called several times by oss emulation
 211         * with different params */
 212
 213        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 214        runtime->dma_bytes = params_buffer_bytes(params);
 215
 216        prtd->params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
 217        prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
 218
 219        prtd->dma_buffer = runtime->dma_addr;
 220        prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
 221        prtd->period_size = params_period_bytes(params);
 222
 223        pr_debug("atmel-pcm: "
 224                "hw_params: DMA for %s initialized "
 225                "(dma_bytes=%zu, period_size=%zu)\n",
 226                prtd->params->name,
 227                runtime->dma_bytes,
 228                prtd->period_size);
 229        return 0;
 230}
 231
 232static int atmel_pcm_hw_free(struct snd_soc_component *component,
 233                             struct snd_pcm_substream *substream)
 234{
 235        struct atmel_runtime_data *prtd = substream->runtime->private_data;
 236        struct atmel_pcm_dma_params *params = prtd->params;
 237
 238        if (params != NULL) {
 239                ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
 240                           params->mask->pdc_disable);
 241                prtd->params->dma_intr_handler = NULL;
 242        }
 243
 244        return 0;
 245}
 246
 247static int atmel_pcm_prepare(struct snd_soc_component *component,
 248                             struct snd_pcm_substream *substream)
 249{
 250        struct atmel_runtime_data *prtd = substream->runtime->private_data;
 251        struct atmel_pcm_dma_params *params = prtd->params;
 252
 253        ssc_writex(params->ssc->regs, SSC_IDR,
 254                   params->mask->ssc_endx | params->mask->ssc_endbuf);
 255        ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
 256                   params->mask->pdc_disable);
 257        return 0;
 258}
 259
 260static int atmel_pcm_trigger(struct snd_soc_component *component,
 261                             struct snd_pcm_substream *substream, int cmd)
 262{
 263        struct snd_pcm_runtime *rtd = substream->runtime;
 264        struct atmel_runtime_data *prtd = rtd->private_data;
 265        struct atmel_pcm_dma_params *params = prtd->params;
 266        int ret = 0;
 267
 268        pr_debug("atmel-pcm:buffer_size = %ld,"
 269                "dma_area = %p, dma_bytes = %zu\n",
 270                rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
 271
 272        switch (cmd) {
 273        case SNDRV_PCM_TRIGGER_START:
 274                prtd->period_ptr = prtd->dma_buffer;
 275
 276                ssc_writex(params->ssc->regs, params->pdc->xpr,
 277                           prtd->period_ptr);
 278                ssc_writex(params->ssc->regs, params->pdc->xcr,
 279                           prtd->period_size / params->pdc_xfer_size);
 280
 281                prtd->period_ptr += prtd->period_size;
 282                ssc_writex(params->ssc->regs, params->pdc->xnpr,
 283                           prtd->period_ptr);
 284                ssc_writex(params->ssc->regs, params->pdc->xncr,
 285                           prtd->period_size / params->pdc_xfer_size);
 286
 287                pr_debug("atmel-pcm: trigger: "
 288                        "period_ptr=%lx, xpr=%u, "
 289                        "xcr=%u, xnpr=%u, xncr=%u\n",
 290                        (unsigned long)prtd->period_ptr,
 291                        ssc_readx(params->ssc->regs, params->pdc->xpr),
 292                        ssc_readx(params->ssc->regs, params->pdc->xcr),
 293                        ssc_readx(params->ssc->regs, params->pdc->xnpr),
 294                        ssc_readx(params->ssc->regs, params->pdc->xncr));
 295
 296                ssc_writex(params->ssc->regs, SSC_IER,
 297                           params->mask->ssc_endx | params->mask->ssc_endbuf);
 298                ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
 299                           params->mask->pdc_enable);
 300
 301                pr_debug("sr=%u imr=%u\n",
 302                        ssc_readx(params->ssc->regs, SSC_SR),
 303                        ssc_readx(params->ssc->regs, SSC_IER));
 304                break;          /* SNDRV_PCM_TRIGGER_START */
 305
 306        case SNDRV_PCM_TRIGGER_STOP:
 307        case SNDRV_PCM_TRIGGER_SUSPEND:
 308        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 309                ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
 310                           params->mask->pdc_disable);
 311                break;
 312
 313        case SNDRV_PCM_TRIGGER_RESUME:
 314        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 315                ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
 316                           params->mask->pdc_enable);
 317                break;
 318
 319        default:
 320                ret = -EINVAL;
 321        }
 322
 323        return ret;
 324}
 325
 326static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component,
 327                                           struct snd_pcm_substream *substream)
 328{
 329        struct snd_pcm_runtime *runtime = substream->runtime;
 330        struct atmel_runtime_data *prtd = runtime->private_data;
 331        struct atmel_pcm_dma_params *params = prtd->params;
 332        dma_addr_t ptr;
 333        snd_pcm_uframes_t x;
 334
 335        ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
 336        x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
 337
 338        if (x == runtime->buffer_size)
 339                x = 0;
 340
 341        return x;
 342}
 343
 344static int atmel_pcm_open(struct snd_soc_component *component,
 345                          struct snd_pcm_substream *substream)
 346{
 347        struct snd_pcm_runtime *runtime = substream->runtime;
 348        struct atmel_runtime_data *prtd;
 349        int ret = 0;
 350
 351        snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
 352
 353        /* ensure that buffer size is a multiple of period size */
 354        ret = snd_pcm_hw_constraint_integer(runtime,
 355                                                SNDRV_PCM_HW_PARAM_PERIODS);
 356        if (ret < 0)
 357                goto out;
 358
 359        prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
 360        if (prtd == NULL) {
 361                ret = -ENOMEM;
 362                goto out;
 363        }
 364        runtime->private_data = prtd;
 365
 366 out:
 367        return ret;
 368}
 369
 370static int atmel_pcm_close(struct snd_soc_component *component,
 371                           struct snd_pcm_substream *substream)
 372{
 373        struct atmel_runtime_data *prtd = substream->runtime->private_data;
 374
 375        kfree(prtd);
 376        return 0;
 377}
 378
 379static const struct snd_soc_component_driver atmel_soc_platform = {
 380        .open           = atmel_pcm_open,
 381        .close          = atmel_pcm_close,
 382        .hw_params      = atmel_pcm_hw_params,
 383        .hw_free        = atmel_pcm_hw_free,
 384        .prepare        = atmel_pcm_prepare,
 385        .trigger        = atmel_pcm_trigger,
 386        .pointer        = atmel_pcm_pointer,
 387        .mmap           = atmel_pcm_mmap,
 388        .pcm_construct  = atmel_pcm_new,
 389        .pcm_destruct   = atmel_pcm_free,
 390};
 391
 392int atmel_pcm_pdc_platform_register(struct device *dev)
 393{
 394        return devm_snd_soc_register_component(dev, &atmel_soc_platform,
 395                                               NULL, 0);
 396}
 397EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
 398
 399MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
 400MODULE_DESCRIPTION("Atmel PCM module");
 401MODULE_LICENSE("GPL");
 402