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        .formats                = SNDRV_PCM_FMTBIT_S16_LE,
  36        .channels_min           = 1,
  37        .channels_max           = 2,
  38        .buffer_bytes_max       = 4*1024,
  39        .period_bytes_min       = 1*1024,
  40        .period_bytes_max       = 4*1024,
  41        .periods_min            = 1,
  42        .periods_max            = 1024,
  43};
  44
  45static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
  46        struct snd_pcm_hw_params *params)
  47{
  48        struct snd_pcm_runtime *runtime = substream->runtime;
  49        struct nuc900_audio *nuc900_audio = runtime->private_data;
  50        unsigned long flags;
  51        int ret = 0;
  52
  53        ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
  54        if (ret < 0)
  55                return ret;
  56
  57        spin_lock_irqsave(&nuc900_audio->lock, flags);
  58
  59        nuc900_audio->substream = substream;
  60        nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr;
  61        nuc900_audio->buffersize[substream->stream] =
  62                                                params_buffer_bytes(params);
  63
  64        spin_unlock_irqrestore(&nuc900_audio->lock, flags);
  65
  66        return ret;
  67}
  68
  69static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
  70                                dma_addr_t dma_addr, size_t count)
  71{
  72        struct snd_pcm_runtime *runtime = substream->runtime;
  73        struct nuc900_audio *nuc900_audio = runtime->private_data;
  74        void __iomem *mmio_addr, *mmio_len;
  75
  76        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  77                mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
  78                mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
  79        } else {
  80                mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
  81                mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
  82        }
  83
  84        AUDIO_WRITE(mmio_addr, dma_addr);
  85        AUDIO_WRITE(mmio_len, count);
  86}
  87
  88static void nuc900_dma_start(struct snd_pcm_substream *substream)
  89{
  90        struct snd_pcm_runtime *runtime = substream->runtime;
  91        struct nuc900_audio *nuc900_audio = runtime->private_data;
  92        unsigned long val;
  93
  94        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
  95        val |= (T_DMA_IRQ | R_DMA_IRQ);
  96        AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
  97}
  98
  99static void nuc900_dma_stop(struct snd_pcm_substream *substream)
 100{
 101        struct snd_pcm_runtime *runtime = substream->runtime;
 102        struct nuc900_audio *nuc900_audio = runtime->private_data;
 103        unsigned long val;
 104
 105        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
 106        val &= ~(T_DMA_IRQ | R_DMA_IRQ);
 107        AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
 108}
 109
 110static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
 111{
 112        struct snd_pcm_substream *substream = dev_id;
 113        struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
 114        unsigned long val;
 115
 116        spin_lock(&nuc900_audio->lock);
 117
 118        val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
 119
 120        if (val & R_DMA_IRQ) {
 121                AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
 122
 123                val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
 124
 125                if (val & R_DMA_MIDDLE_IRQ) {
 126                        val |= R_DMA_MIDDLE_IRQ;
 127                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
 128                }
 129
 130                if (val & R_DMA_END_IRQ) {
 131                        val |= R_DMA_END_IRQ;
 132                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
 133                }
 134        } else if (val & T_DMA_IRQ) {
 135                AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
 136
 137                val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
 138
 139                if (val & P_DMA_MIDDLE_IRQ) {
 140                        val |= P_DMA_MIDDLE_IRQ;
 141                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
 142                }
 143
 144                if (val & P_DMA_END_IRQ) {
 145                        val |= P_DMA_END_IRQ;
 146                        AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
 147                }
 148        } else {
 149                dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
 150                spin_unlock(&nuc900_audio->lock);
 151                return IRQ_HANDLED;
 152        }
 153
 154        spin_unlock(&nuc900_audio->lock);
 155
 156        snd_pcm_period_elapsed(substream);
 157
 158        return IRQ_HANDLED;
 159}
 160
 161static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
 162{
 163        snd_pcm_lib_free_pages(substream);
 164        return 0;
 165}
 166
 167static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
 168{
 169        struct snd_pcm_runtime *runtime = substream->runtime;
 170        struct nuc900_audio *nuc900_audio = runtime->private_data;
 171        unsigned long flags, val;
 172        int ret = 0;
 173
 174        spin_lock_irqsave(&nuc900_audio->lock, flags);
 175
 176        nuc900_update_dma_register(substream,
 177                                nuc900_audio->dma_addr[substream->stream],
 178                                nuc900_audio->buffersize[substream->stream]);
 179
 180        val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
 181
 182        switch (runtime->channels) {
 183        case 1:
 184                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 185                        val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
 186                        val |= PLAY_RIGHT_CHNNEL;
 187                } else {
 188                        val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
 189                        val |= RECORD_RIGHT_CHNNEL;
 190                }
 191                AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
 192                break;
 193        case 2:
 194                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 195                        val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
 196                else
 197                        val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
 198                AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
 199                break;
 200        default:
 201                ret = -EINVAL;
 202        }
 203        spin_unlock_irqrestore(&nuc900_audio->lock, flags);
 204        return ret;
 205}
 206
 207static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
 208{
 209        int ret = 0;
 210
 211        switch (cmd) {
 212        case SNDRV_PCM_TRIGGER_START:
 213        case SNDRV_PCM_TRIGGER_RESUME:
 214                nuc900_dma_start(substream);
 215                break;
 216
 217        case SNDRV_PCM_TRIGGER_STOP:
 218        case SNDRV_PCM_TRIGGER_SUSPEND:
 219                nuc900_dma_stop(substream);
 220                break;
 221
 222        default:
 223                ret = -EINVAL;
 224                break;
 225        }
 226
 227        return ret;
 228}
 229
 230static int nuc900_dma_getposition(struct snd_pcm_substream *substream,
 231                                        dma_addr_t *src, dma_addr_t *dst)
 232{
 233        struct snd_pcm_runtime *runtime = substream->runtime;
 234        struct nuc900_audio *nuc900_audio = runtime->private_data;
 235
 236        if (src != NULL)
 237                *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
 238
 239        if (dst != NULL)
 240                *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
 241
 242        return 0;
 243}
 244
 245static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
 246{
 247        struct snd_pcm_runtime *runtime = substream->runtime;
 248        dma_addr_t src, dst;
 249        unsigned long res;
 250
 251        nuc900_dma_getposition(substream, &src, &dst);
 252
 253        if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
 254                res = dst - runtime->dma_addr;
 255        else
 256                res = src - runtime->dma_addr;
 257
 258        return bytes_to_frames(substream->runtime, res);
 259}
 260
 261static int nuc900_dma_open(struct snd_pcm_substream *substream)
 262{
 263        struct snd_pcm_runtime *runtime = substream->runtime;
 264        struct nuc900_audio *nuc900_audio;
 265
 266        snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
 267
 268        nuc900_audio = nuc900_ac97_data;
 269
 270        if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
 271                        0, "nuc900-dma", substream))
 272                return -EBUSY;
 273
 274        runtime->private_data = nuc900_audio;
 275
 276        return 0;
 277}
 278
 279static int nuc900_dma_close(struct snd_pcm_substream *substream)
 280{
 281        struct snd_pcm_runtime *runtime = substream->runtime;
 282        struct nuc900_audio *nuc900_audio = runtime->private_data;
 283
 284        free_irq(nuc900_audio->irq_num, substream);
 285
 286        return 0;
 287}
 288
 289static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
 290        struct vm_area_struct *vma)
 291{
 292        struct snd_pcm_runtime *runtime = substream->runtime;
 293
 294        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 295                                        runtime->dma_area,
 296                                        runtime->dma_addr,
 297                                        runtime->dma_bytes);
 298}
 299
 300static struct snd_pcm_ops nuc900_dma_ops = {
 301        .open           = nuc900_dma_open,
 302        .close          = nuc900_dma_close,
 303        .ioctl          = snd_pcm_lib_ioctl,
 304        .hw_params      = nuc900_dma_hw_params,
 305        .hw_free        = nuc900_dma_hw_free,
 306        .prepare        = nuc900_dma_prepare,
 307        .trigger        = nuc900_dma_trigger,
 308        .pointer        = nuc900_dma_pointer,
 309        .mmap           = nuc900_dma_mmap,
 310};
 311
 312static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
 313{
 314        snd_pcm_lib_preallocate_free_for_all(pcm);
 315}
 316
 317static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
 318static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd)
 319{
 320        struct snd_card *card = rtd->card->snd_card;
 321        struct snd_pcm *pcm = rtd->pcm;
 322
 323        if (!card->dev->dma_mask)
 324                card->dev->dma_mask = &nuc900_pcm_dmamask;
 325        if (!card->dev->coherent_dma_mask)
 326                card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
 327
 328        snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 329                card->dev, 4 * 1024, (4 * 1024) - 1);
 330
 331        return 0;
 332}
 333
 334static struct snd_soc_platform_driver nuc900_soc_platform = {
 335        .ops            = &nuc900_dma_ops,
 336        .pcm_new        = nuc900_dma_new,
 337        .pcm_free       = nuc900_dma_free_dma_buffers,
 338};
 339
 340static int nuc900_soc_platform_probe(struct platform_device *pdev)
 341{
 342        return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform);
 343}
 344
 345static int nuc900_soc_platform_remove(struct platform_device *pdev)
 346{
 347        snd_soc_unregister_platform(&pdev->dev);
 348        return 0;
 349}
 350
 351static struct platform_driver nuc900_pcm_driver = {
 352        .driver = {
 353                        .name = "nuc900-pcm-audio",
 354                        .owner = THIS_MODULE,
 355        },
 356
 357        .probe = nuc900_soc_platform_probe,
 358        .remove = nuc900_soc_platform_remove,
 359};
 360
 361module_platform_driver(nuc900_pcm_driver);
 362
 363MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
 364MODULE_DESCRIPTION("nuc900 Audio DMA module");
 365MODULE_LICENSE("GPL");
 366