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