linux/sound/soc/ep93xx/ep93xx-pcm.c
<<
>>
Prefs
   1/*
   2 * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
   3 *
   4 * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
   5 * Copyright (C) 2006 Applied Data Systems
   6 *
   7 * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
   8 *   Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.com>
   9 *
  10 * This program is free software; you can redistribute it and/or modify
  11 * it under the terms of the GNU General Public License version 2 as
  12 * published by the Free Software Foundation.
  13 */
  14
  15#include <linux/module.h>
  16#include <linux/init.h>
  17#include <linux/device.h>
  18#include <linux/slab.h>
  19#include <linux/dma-mapping.h>
  20
  21#include <sound/core.h>
  22#include <sound/pcm.h>
  23#include <sound/pcm_params.h>
  24#include <sound/soc.h>
  25
  26#include <mach/dma.h>
  27#include <mach/hardware.h>
  28#include <mach/ep93xx-regs.h>
  29
  30#include "ep93xx-pcm.h"
  31
  32static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
  33        .info                   = (SNDRV_PCM_INFO_MMAP          |
  34                                   SNDRV_PCM_INFO_MMAP_VALID    |
  35                                   SNDRV_PCM_INFO_INTERLEAVED   |
  36                                   SNDRV_PCM_INFO_BLOCK_TRANSFER),
  37                                   
  38        .rates                  = SNDRV_PCM_RATE_8000_192000,
  39        .rate_min               = SNDRV_PCM_RATE_8000,
  40        .rate_max               = SNDRV_PCM_RATE_192000,
  41        
  42        .formats                = (SNDRV_PCM_FMTBIT_S16_LE |
  43                                   SNDRV_PCM_FMTBIT_S24_LE |
  44                                   SNDRV_PCM_FMTBIT_S32_LE),
  45        
  46        .buffer_bytes_max       = 131072,
  47        .period_bytes_min       = 32,
  48        .period_bytes_max       = 32768,
  49        .periods_min            = 1,
  50        .periods_max            = 32,
  51        .fifo_size              = 32,
  52};
  53
  54struct ep93xx_runtime_data
  55{
  56        struct ep93xx_dma_m2p_client    cl;
  57        struct ep93xx_pcm_dma_params    *params;
  58        int                             pointer_bytes;
  59        struct tasklet_struct           period_tasklet;
  60        int                             periods;
  61        struct ep93xx_dma_buffer        buf[32];
  62};
  63
  64static void ep93xx_pcm_period_elapsed(unsigned long data)
  65{
  66        struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
  67        snd_pcm_period_elapsed(substream);
  68}
  69
  70static void ep93xx_pcm_buffer_started(void *cookie,
  71                                      struct ep93xx_dma_buffer *buf)
  72{
  73}
  74
  75static void ep93xx_pcm_buffer_finished(void *cookie, 
  76                                       struct ep93xx_dma_buffer *buf, 
  77                                       int bytes, int error)
  78{
  79        struct snd_pcm_substream *substream = cookie;
  80        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
  81
  82        if (buf == rtd->buf + rtd->periods - 1)
  83                rtd->pointer_bytes = 0;
  84        else
  85                rtd->pointer_bytes += buf->size;
  86
  87        if (!error) {
  88                ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf);
  89                tasklet_schedule(&rtd->period_tasklet);
  90        } else {
  91                snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
  92        }
  93}
  94
  95static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
  96{
  97        struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
  98        struct snd_soc_dai *cpu_dai = soc_rtd->cpu_dai;
  99        struct ep93xx_pcm_dma_params *dma_params;
 100        struct ep93xx_runtime_data *rtd;    
 101        int ret;
 102
 103        dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
 104        snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
 105
 106        rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
 107        if (!rtd) 
 108                return -ENOMEM;
 109
 110        memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet));
 111        rtd->period_tasklet.func = ep93xx_pcm_period_elapsed;
 112        rtd->period_tasklet.data = (unsigned long)substream;
 113
 114        rtd->cl.name = dma_params->name;
 115        rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR |
 116                ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
 117                 EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX);
 118        rtd->cl.cookie = substream;
 119        rtd->cl.buffer_started = ep93xx_pcm_buffer_started;
 120        rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished;
 121        ret = ep93xx_dma_m2p_client_register(&rtd->cl);
 122        if (ret < 0) {
 123                kfree(rtd);
 124                return ret;
 125        }
 126        
 127        substream->runtime->private_data = rtd;
 128        return 0;
 129}
 130
 131static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
 132{
 133        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
 134
 135        ep93xx_dma_m2p_client_unregister(&rtd->cl);
 136        kfree(rtd);
 137        return 0;
 138}
 139
 140static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
 141                                struct snd_pcm_hw_params *params)
 142{
 143        struct snd_pcm_runtime *runtime = substream->runtime;
 144        struct ep93xx_runtime_data *rtd = runtime->private_data;
 145        size_t totsize = params_buffer_bytes(params);
 146        size_t period = params_period_bytes(params);
 147        int i;
 148
 149        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 150        runtime->dma_bytes = totsize;
 151
 152        rtd->periods = (totsize + period - 1) / period;
 153        for (i = 0; i < rtd->periods; i++) {
 154                rtd->buf[i].bus_addr = runtime->dma_addr + (i * period);
 155                rtd->buf[i].size = period;
 156                if ((i + 1) * period > totsize)
 157                        rtd->buf[i].size = totsize - (i * period);
 158        }
 159
 160        return 0;
 161}
 162
 163static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
 164{
 165        snd_pcm_set_runtime_buffer(substream, NULL);
 166        return 0;
 167}
 168
 169static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 170{
 171        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
 172        int ret;
 173        int i;
 174
 175        ret = 0;
 176        switch (cmd) {
 177        case SNDRV_PCM_TRIGGER_START:
 178        case SNDRV_PCM_TRIGGER_RESUME:
 179        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 180                rtd->pointer_bytes = 0;
 181                for (i = 0; i < rtd->periods; i++)
 182                        ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i);
 183                break;
 184
 185        case SNDRV_PCM_TRIGGER_STOP:
 186        case SNDRV_PCM_TRIGGER_SUSPEND:
 187        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 188                ep93xx_dma_m2p_flush(&rtd->cl);
 189                break;
 190
 191        default:
 192                ret = -EINVAL;
 193                break;
 194        }
 195
 196        return ret;
 197}
 198
 199static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
 200{
 201        struct snd_pcm_runtime *runtime = substream->runtime;
 202        struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
 203
 204        /* FIXME: implement this with sub-period granularity */
 205        return bytes_to_frames(runtime, rtd->pointer_bytes);
 206}
 207
 208static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream,
 209                           struct vm_area_struct *vma)
 210{
 211        struct snd_pcm_runtime *runtime = substream->runtime;
 212
 213        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
 214                                     runtime->dma_area,
 215                                     runtime->dma_addr,
 216                                     runtime->dma_bytes);
 217}
 218
 219static struct snd_pcm_ops ep93xx_pcm_ops = {
 220        .open           = ep93xx_pcm_open,
 221        .close          = ep93xx_pcm_close,
 222        .ioctl          = snd_pcm_lib_ioctl,
 223        .hw_params      = ep93xx_pcm_hw_params,
 224        .hw_free        = ep93xx_pcm_hw_free,
 225        .trigger        = ep93xx_pcm_trigger,
 226        .pointer        = ep93xx_pcm_pointer,
 227        .mmap           = ep93xx_pcm_mmap,
 228};
 229
 230static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 231{
 232        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 233        struct snd_dma_buffer *buf = &substream->dma_buffer;
 234        size_t size = ep93xx_pcm_hardware.buffer_bytes_max;
 235
 236        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 237        buf->dev.dev = pcm->card->dev;
 238        buf->private_data = NULL;
 239        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
 240                                           &buf->addr, GFP_KERNEL);
 241        buf->bytes = size;
 242
 243        return (buf->area == NULL) ? -ENOMEM : 0;
 244}
 245
 246static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
 247{
 248        struct snd_pcm_substream *substream;
 249        struct snd_dma_buffer *buf;
 250        int stream;
 251
 252        for (stream = 0; stream < 2; stream++) {                
 253                substream = pcm->streams[stream].substream;
 254                if (!substream)
 255                        continue;
 256                
 257                buf = &substream->dma_buffer;
 258                if (!buf->area)
 259                        continue;
 260
 261                dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area,
 262                                      buf->addr);
 263                buf->area = NULL;
 264        }
 265}
 266
 267static u64 ep93xx_pcm_dmamask = 0xffffffff;
 268
 269static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
 270                          struct snd_pcm *pcm)
 271{
 272        int ret = 0;
 273
 274        if (!card->dev->dma_mask)
 275                card->dev->dma_mask = &ep93xx_pcm_dmamask;
 276        if (!card->dev->coherent_dma_mask)
 277                card->dev->coherent_dma_mask = 0xffffffff;
 278
 279        if (dai->driver->playback.channels_min) {
 280                ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
 281                                        SNDRV_PCM_STREAM_PLAYBACK);
 282                if (ret)
 283                        return ret;
 284        }
 285
 286        if (dai->driver->capture.channels_min) {
 287                ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
 288                                        SNDRV_PCM_STREAM_CAPTURE);
 289                if (ret)
 290                        return ret;
 291        }
 292
 293        return 0;
 294}
 295
 296static struct snd_soc_platform_driver ep93xx_soc_platform = {
 297        .ops            = &ep93xx_pcm_ops,
 298        .pcm_new        = &ep93xx_pcm_new,
 299        .pcm_free       = &ep93xx_pcm_free_dma_buffers,
 300};
 301
 302static int __devinit ep93xx_soc_platform_probe(struct platform_device *pdev)
 303{
 304        return snd_soc_register_platform(&pdev->dev, &ep93xx_soc_platform);
 305}
 306
 307static int __devexit ep93xx_soc_platform_remove(struct platform_device *pdev)
 308{
 309        snd_soc_unregister_platform(&pdev->dev);
 310        return 0;
 311}
 312
 313static struct platform_driver ep93xx_pcm_driver = {
 314        .driver = {
 315                        .name = "ep93xx-pcm-audio",
 316                        .owner = THIS_MODULE,
 317        },
 318
 319        .probe = ep93xx_soc_platform_probe,
 320        .remove = __devexit_p(ep93xx_soc_platform_remove),
 321};
 322
 323static int __init ep93xx_soc_platform_init(void)
 324{
 325        return platform_driver_register(&ep93xx_pcm_driver);
 326}
 327
 328static void __exit ep93xx_soc_platform_exit(void)
 329{
 330        platform_driver_unregister(&ep93xx_pcm_driver);
 331}
 332
 333module_init(ep93xx_soc_platform_init);
 334module_exit(ep93xx_soc_platform_exit);
 335
 336MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
 337MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
 338MODULE_LICENSE("GPL");
 339