linux/sound/soc/nuc900/nuc900-pcm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2010 Nuvoton technology corporation.
   4 *
   5 * Wan ZongShun <mcuos.com@gmail.com>
   6 */
   7
   8#include <linux/module.h>
   9#include <linux/init.h>
  10#include <linux/io.h>
  11#include <linux/platform_device.h>
  12#include <linux/slab.h>
  13#include <linux/dma-mapping.h>
  14
  15#include <sound/core.h>
  16#include <sound/pcm.h>
  17#include <sound/pcm_params.h>
  18#include <sound/soc.h>
  19
  20#include <mach/hardware.h>
  21
  22#include "nuc900-audio.h"
  23
  24static const struct snd_pcm_hardware nuc900_pcm_hardware = {
  25        .info                   = SNDRV_PCM_INFO_INTERLEAVED |
  26                                        SNDRV_PCM_INFO_BLOCK_TRANSFER |
  27                                        SNDRV_PCM_INFO_MMAP |
  28                                        SNDRV_PCM_INFO_MMAP_VALID |
  29                                        SNDRV_PCM_INFO_PAUSE |
  30                                        SNDRV_PCM_INFO_RESUME,
  31        .buffer_bytes_max       = 4*1024,
  32        .period_bytes_min       = 1*1024,
  33        .period_bytes_max       = 4*1024,
  34        .periods_min            = 1,
  35        .periods_max            = 1024,
  36};
  37
  38static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
  39        struct snd_pcm_hw_params *params)
  40{
  41        return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
  42}
  43
  44static void nuc900_update_dma_register(struct snd_pcm_substream *substream)
  45{
  46        struct snd_pcm_runtime *runtime = substream->runtime;
  47        struct nuc900_audio *nuc900_audio = runtime->private_data;
  48        void __iomem *mmio_addr, *mmio_len;
  49
  50        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  51                mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
  52                mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
  53        } else {
  54                mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
  55                mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
  56        }
  57
  58        AUDIO_WRITE(mmio_addr, runtime->dma_addr);
  59        AUDIO_WRITE(mmio_len, runtime->dma_bytes);
  60}
  61
  62static void nuc900_dma_start(struct snd_pcm_substream *substream)
  63{
  64        struct snd_pcm_runtime *runtime = substream->runtime;
  65        struct nuc900_audio *nuc900_audio = runtime->private_data;
  66        unsigned long val;
  67
  68        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
  69        val |= (T_DMA_IRQ | R_DMA_IRQ);
  70        AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
  71}
  72
  73static void nuc900_dma_stop(struct snd_pcm_substream *substream)
  74{
  75        struct snd_pcm_runtime *runtime = substream->runtime;
  76        struct nuc900_audio *nuc900_audio = runtime->private_data;
  77        unsigned long val;
  78
  79        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
  80        val &= ~(T_DMA_IRQ | R_DMA_IRQ);
  81        AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
  82}
  83
  84static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
  85{
  86        struct snd_pcm_substream *substream = dev_id;
  87        struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
  88        unsigned long val;
  89
  90        spin_lock(&nuc900_audio->lock);
  91
  92        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
  93
  94        if (val & R_DMA_IRQ) {
  95                AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
  96
  97                val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
  98
  99                if (val & R_DMA_MIDDLE_IRQ) {
 100                        val |= R_DMA_MIDDLE_IRQ;
 101                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
 102                }
 103
 104                if (val & R_DMA_END_IRQ) {
 105                        val |= R_DMA_END_IRQ;
 106                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
 107                }
 108        } else if (val & T_DMA_IRQ) {
 109                AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
 110
 111                val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
 112
 113                if (val & P_DMA_MIDDLE_IRQ) {
 114                        val |= P_DMA_MIDDLE_IRQ;
 115                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
 116                }
 117
 118                if (val & P_DMA_END_IRQ) {
 119                        val |= P_DMA_END_IRQ;
 120                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
 121                }
 122        } else {
 123                dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
 124                spin_unlock(&nuc900_audio->lock);
 125                return IRQ_HANDLED;
 126        }
 127
 128        spin_unlock(&nuc900_audio->lock);
 129
 130        snd_pcm_period_elapsed(substream);
 131
 132        return IRQ_HANDLED;
 133}
 134
 135static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
 136{
 137        snd_pcm_lib_free_pages(substream);
 138        return 0;
 139}
 140
 141static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
 142{
 143        struct snd_pcm_runtime *runtime = substream->runtime;
 144        struct nuc900_audio *nuc900_audio = runtime->private_data;
 145        unsigned long flags, val;
 146        int ret = 0;
 147
 148        spin_lock_irqsave(&nuc900_audio->lock, flags);
 149
 150        nuc900_update_dma_register(substream);
 151
 152        val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
 153
 154        switch (runtime->channels) {
 155        case 1:
 156                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 157                        val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
 158                        val |= PLAY_RIGHT_CHNNEL;
 159                } else {
 160                        val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
 161                        val |= RECORD_RIGHT_CHNNEL;
 162                }
 163                AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
 164                break;
 165        case 2:
 166                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 167                        val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
 168                else
 169                        val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
 170                AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
 171                break;
 172        default:
 173                ret = -EINVAL;
 174        }
 175        spin_unlock_irqrestore(&nuc900_audio->lock, flags);
 176        return ret;
 177}
 178
 179static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
 180{
 181        int ret = 0;
 182
 183        switch (cmd) {
 184        case SNDRV_PCM_TRIGGER_START:
 185        case SNDRV_PCM_TRIGGER_RESUME:
 186                nuc900_dma_start(substream);
 187                break;
 188
 189        case SNDRV_PCM_TRIGGER_STOP:
 190        case SNDRV_PCM_TRIGGER_SUSPEND:
 191                nuc900_dma_stop(substream);
 192                break;
 193
 194        default:
 195                ret = -EINVAL;
 196                break;
 197        }
 198
 199        return ret;
 200}
 201
 202static int nuc900_dma_getposition(struct snd_pcm_substream *substream,
 203                                        dma_addr_t *src, dma_addr_t *dst)
 204{
 205        struct snd_pcm_runtime *runtime = substream->runtime;
 206        struct nuc900_audio *nuc900_audio = runtime->private_data;
 207
 208        if (src != NULL)
 209                *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
 210
 211        if (dst != NULL)
 212                *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
 213
 214        return 0;
 215}
 216
 217static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
 218{
 219        struct snd_pcm_runtime *runtime = substream->runtime;
 220        dma_addr_t src, dst;
 221        unsigned long res;
 222
 223        nuc900_dma_getposition(substream, &src, &dst);
 224
 225        if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
 226                res = dst - runtime->dma_addr;
 227        else
 228                res = src - runtime->dma_addr;
 229
 230        return bytes_to_frames(substream->runtime, res);
 231}
 232
 233static int nuc900_dma_open(struct snd_pcm_substream *substream)
 234{
 235        struct snd_pcm_runtime *runtime = substream->runtime;
 236        struct nuc900_audio *nuc900_audio;
 237
 238        snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
 239
 240        nuc900_audio = nuc900_ac97_data;
 241
 242        if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
 243                        0, "nuc900-dma", substream))
 244                return -EBUSY;
 245
 246        runtime->private_data = nuc900_audio;
 247
 248        return 0;
 249}
 250
 251static int nuc900_dma_close(struct snd_pcm_substream *substream)
 252{
 253        struct snd_pcm_runtime *runtime = substream->runtime;
 254        struct nuc900_audio *nuc900_audio = runtime->private_data;
 255
 256        free_irq(nuc900_audio->irq_num, substream);
 257
 258        return 0;
 259}
 260
 261static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
 262        struct vm_area_struct *vma)
 263{
 264        struct snd_pcm_runtime *runtime = substream->runtime;
 265
 266        return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area,
 267                           runtime->dma_addr, runtime->dma_bytes);
 268}
 269
 270static const struct snd_pcm_ops nuc900_dma_ops = {
 271        .open           = nuc900_dma_open,
 272        .close          = nuc900_dma_close,
 273        .ioctl          = snd_pcm_lib_ioctl,
 274        .hw_params      = nuc900_dma_hw_params,
 275        .hw_free        = nuc900_dma_hw_free,
 276        .prepare        = nuc900_dma_prepare,
 277        .trigger        = nuc900_dma_trigger,
 278        .pointer        = nuc900_dma_pointer,
 279        .mmap           = nuc900_dma_mmap,
 280};
 281
 282static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd)
 283{
 284        struct snd_card *card = rtd->card->snd_card;
 285        struct snd_pcm *pcm = rtd->pcm;
 286        int ret;
 287
 288        ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
 289        if (ret)
 290                return ret;
 291
 292        snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 293                card->dev, 4 * 1024, (4 * 1024) - 1);
 294
 295        return 0;
 296}
 297
 298static const struct snd_soc_component_driver nuc900_soc_component = {
 299        .ops            = &nuc900_dma_ops,
 300        .pcm_new        = nuc900_dma_new,
 301};
 302
 303static int nuc900_soc_platform_probe(struct platform_device *pdev)
 304{
 305        return devm_snd_soc_register_component(&pdev->dev, &nuc900_soc_component,
 306                                               NULL, 0);
 307}
 308
 309static struct platform_driver nuc900_pcm_driver = {
 310        .driver = {
 311                        .name = "nuc900-pcm-audio",
 312        },
 313
 314        .probe = nuc900_soc_platform_probe,
 315};
 316
 317module_platform_driver(nuc900_pcm_driver);
 318
 319MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
 320MODULE_DESCRIPTION("nuc900 Audio DMA module");
 321MODULE_LICENSE("GPL");
 322