linux/sound/soc/blackfin/bf5xx-i2s-pcm.c
<<
>>
Prefs
   1/*
   2 * File:         sound/soc/blackfin/bf5xx-i2s-pcm.c
   3 * Author:       Cliff Cai <Cliff.Cai@analog.com>
   4 *
   5 * Created:      Tue June 06 2008
   6 * Description:  DMA driver for i2s codec
   7 *
   8 * Modified:
   9 *               Copyright 2008 Analog Devices Inc.
  10 *
  11 * Bugs:         Enter bugs at http://blackfin.uclinux.org/
  12 *
  13 * This program is free software; you can redistribute it and/or modify
  14 * it under the terms of the GNU General Public License as published by
  15 * the Free Software Foundation; either version 2 of the License, or
  16 * (at your option) any later version.
  17 *
  18 * This program is distributed in the hope that it will be useful,
  19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21 * GNU General Public License for more details.
  22 *
  23 * You should have received a copy of the GNU General Public License
  24 * along with this program; if not, see the file COPYING, or write
  25 * to the Free Software Foundation, Inc.,
  26 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  27 */
  28
  29#include <linux/module.h>
  30#include <linux/init.h>
  31#include <linux/platform_device.h>
  32#include <linux/dma-mapping.h>
  33#include <linux/gfp.h>
  34
  35#include <sound/core.h>
  36#include <sound/pcm.h>
  37#include <sound/pcm_params.h>
  38#include <sound/soc.h>
  39
  40#include <asm/dma.h>
  41
  42#include "bf5xx-i2s-pcm.h"
  43#include "bf5xx-sport.h"
  44
  45static void bf5xx_dma_irq(void *data)
  46{
  47        struct snd_pcm_substream *pcm = data;
  48        snd_pcm_period_elapsed(pcm);
  49}
  50
  51static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
  52        .info                   = SNDRV_PCM_INFO_INTERLEAVED |
  53                                   SNDRV_PCM_INFO_MMAP |
  54                                   SNDRV_PCM_INFO_MMAP_VALID |
  55                                   SNDRV_PCM_INFO_BLOCK_TRANSFER,
  56        .formats                = SNDRV_PCM_FMTBIT_S16_LE |
  57                                   SNDRV_PCM_FMTBIT_S24_LE |
  58                                   SNDRV_PCM_FMTBIT_S32_LE,
  59        .period_bytes_min       = 32,
  60        .period_bytes_max       = 0x10000,
  61        .periods_min            = 1,
  62        .periods_max            = PAGE_SIZE/32,
  63        .buffer_bytes_max       = 0x20000, /* 128 kbytes */
  64        .fifo_size              = 16,
  65};
  66
  67static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
  68        struct snd_pcm_hw_params *params)
  69{
  70        size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
  71        snd_pcm_lib_malloc_pages(substream, size);
  72
  73        return 0;
  74}
  75
  76static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
  77{
  78        snd_pcm_lib_free_pages(substream);
  79
  80        return 0;
  81}
  82
  83static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
  84{
  85        struct snd_pcm_runtime *runtime = substream->runtime;
  86        struct sport_device *sport = runtime->private_data;
  87        int period_bytes = frames_to_bytes(runtime, runtime->period_size);
  88
  89        pr_debug("%s enter\n", __func__);
  90        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  91                sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
  92                sport_config_tx_dma(sport, runtime->dma_area,
  93                        runtime->periods, period_bytes);
  94        } else {
  95                sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
  96                sport_config_rx_dma(sport, runtime->dma_area,
  97                        runtime->periods, period_bytes);
  98        }
  99
 100        return 0;
 101}
 102
 103static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 104{
 105        struct snd_pcm_runtime *runtime = substream->runtime;
 106        struct sport_device *sport = runtime->private_data;
 107        int ret = 0;
 108
 109        pr_debug("%s enter\n", __func__);
 110        switch (cmd) {
 111        case SNDRV_PCM_TRIGGER_START:
 112                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 113                        sport_tx_start(sport);
 114                else
 115                        sport_rx_start(sport);
 116                break;
 117        case SNDRV_PCM_TRIGGER_STOP:
 118        case SNDRV_PCM_TRIGGER_SUSPEND:
 119        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 120                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 121                        sport_tx_stop(sport);
 122                else
 123                        sport_rx_stop(sport);
 124                break;
 125        default:
 126                ret = -EINVAL;
 127        }
 128
 129        return ret;
 130}
 131
 132static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
 133{
 134        struct snd_pcm_runtime *runtime = substream->runtime;
 135        struct sport_device *sport = runtime->private_data;
 136        unsigned int diff;
 137        snd_pcm_uframes_t frames;
 138        pr_debug("%s enter\n", __func__);
 139        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 140                diff = sport_curr_offset_tx(sport);
 141        } else {
 142                diff = sport_curr_offset_rx(sport);
 143        }
 144
 145        /*
 146         * TX at least can report one frame beyond the end of the
 147         * buffer if we hit the wraparound case - clamp to within the
 148         * buffer as the ALSA APIs require.
 149         */
 150        if (diff == snd_pcm_lib_buffer_bytes(substream))
 151                diff = 0;
 152
 153        frames = bytes_to_frames(substream->runtime, diff);
 154
 155        return frames;
 156}
 157
 158static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
 159{
 160        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 161        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 162        struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
 163        struct snd_pcm_runtime *runtime = substream->runtime;
 164        struct snd_dma_buffer *buf = &substream->dma_buffer;
 165        int ret;
 166
 167        pr_debug("%s enter\n", __func__);
 168
 169        snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
 170
 171        ret = snd_pcm_hw_constraint_integer(runtime,
 172                        SNDRV_PCM_HW_PARAM_PERIODS);
 173        if (ret < 0)
 174                goto out;
 175
 176        if (sport_handle != NULL) {
 177                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 178                        sport_handle->tx_buf = buf->area;
 179                else
 180                        sport_handle->rx_buf = buf->area;
 181
 182                runtime->private_data = sport_handle;
 183        } else {
 184                pr_err("sport_handle is NULL\n");
 185                return -1;
 186        }
 187        return 0;
 188
 189 out:
 190        return ret;
 191}
 192
 193static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
 194        struct vm_area_struct *vma)
 195{
 196        struct snd_pcm_runtime *runtime = substream->runtime;
 197        size_t size = vma->vm_end - vma->vm_start;
 198        vma->vm_start = (unsigned long)runtime->dma_area;
 199        vma->vm_end = vma->vm_start + size;
 200        vma->vm_flags |=  VM_SHARED;
 201
 202        return 0 ;
 203}
 204
 205static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
 206        .open           = bf5xx_pcm_open,
 207        .ioctl          = snd_pcm_lib_ioctl,
 208        .hw_params      = bf5xx_pcm_hw_params,
 209        .hw_free        = bf5xx_pcm_hw_free,
 210        .prepare        = bf5xx_pcm_prepare,
 211        .trigger        = bf5xx_pcm_trigger,
 212        .pointer        = bf5xx_pcm_pointer,
 213        .mmap           = bf5xx_pcm_mmap,
 214};
 215
 216static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 217{
 218        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 219        struct snd_dma_buffer *buf = &substream->dma_buffer;
 220        size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
 221
 222        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 223        buf->dev.dev = pcm->card->dev;
 224        buf->private_data = NULL;
 225        buf->area = dma_alloc_coherent(pcm->card->dev, size,
 226                        &buf->addr, GFP_KERNEL);
 227        if (!buf->area) {
 228                pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
 229                return -ENOMEM;
 230        }
 231        buf->bytes = size;
 232
 233        pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
 234                buf->area, buf->bytes);
 235
 236        return 0;
 237}
 238
 239static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
 240{
 241        struct snd_pcm_substream *substream;
 242        struct snd_dma_buffer *buf;
 243        int stream;
 244
 245        for (stream = 0; stream < 2; stream++) {
 246                substream = pcm->streams[stream].substream;
 247                if (!substream)
 248                        continue;
 249
 250                buf = &substream->dma_buffer;
 251                if (!buf->area)
 252                        continue;
 253                dma_free_coherent(NULL, buf->bytes, buf->area, 0);
 254                buf->area = NULL;
 255        }
 256}
 257
 258static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
 259
 260static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
 261{
 262        struct snd_card *card = rtd->card->snd_card;
 263        struct snd_pcm *pcm = rtd->pcm;
 264        int ret = 0;
 265
 266        pr_debug("%s enter\n", __func__);
 267        if (!card->dev->dma_mask)
 268                card->dev->dma_mask = &bf5xx_pcm_dmamask;
 269        if (!card->dev->coherent_dma_mask)
 270                card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
 271
 272        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 273                ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
 274                        SNDRV_PCM_STREAM_PLAYBACK);
 275                if (ret)
 276                        goto out;
 277        }
 278
 279        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 280                ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
 281                        SNDRV_PCM_STREAM_CAPTURE);
 282                if (ret)
 283                        goto out;
 284        }
 285 out:
 286        return ret;
 287}
 288
 289static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
 290        .ops            = &bf5xx_pcm_i2s_ops,
 291        .pcm_new        = bf5xx_pcm_i2s_new,
 292        .pcm_free       = bf5xx_pcm_free_dma_buffers,
 293};
 294
 295static int __devinit bfin_i2s_soc_platform_probe(struct platform_device *pdev)
 296{
 297        return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
 298}
 299
 300static int __devexit bfin_i2s_soc_platform_remove(struct platform_device *pdev)
 301{
 302        snd_soc_unregister_platform(&pdev->dev);
 303        return 0;
 304}
 305
 306static struct platform_driver bfin_i2s_pcm_driver = {
 307        .driver = {
 308                .name = "bfin-i2s-pcm-audio",
 309                .owner = THIS_MODULE,
 310        },
 311
 312        .probe = bfin_i2s_soc_platform_probe,
 313        .remove = __devexit_p(bfin_i2s_soc_platform_remove),
 314};
 315
 316module_platform_driver(bfin_i2s_pcm_driver);
 317
 318MODULE_AUTHOR("Cliff Cai");
 319MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
 320MODULE_LICENSE("GPL");
 321
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.