linux/sound/soc/blackfin/bf5xx-tdm-pcm.c
<<
>>
Prefs
   1/*
   2 * File:         sound/soc/blackfin/bf5xx-tdm-pcm.c
   3 * Author:       Barry Song <Barry.Song@analog.com>
   4 *
   5 * Created:      Tue June 06 2009
   6 * Description:  DMA driver for tdm codec
   7 *
   8 * Modified:
   9 *               Copyright 2009 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-tdm-pcm.h"
  43#include "bf5xx-tdm.h"
  44#include "bf5xx-sport.h"
  45
  46#define PCM_BUFFER_MAX  0x8000
  47#define FRAGMENT_SIZE_MIN  (4*1024)
  48#define FRAGMENTS_MIN  2
  49#define FRAGMENTS_MAX  32
  50
  51static void bf5xx_dma_irq(void *data)
  52{
  53        struct snd_pcm_substream *pcm = data;
  54        snd_pcm_period_elapsed(pcm);
  55}
  56
  57static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
  58        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
  59                SNDRV_PCM_INFO_RESUME),
  60        .formats =          SNDRV_PCM_FMTBIT_S32_LE,
  61        .rates =            SNDRV_PCM_RATE_48000,
  62        .channels_min =     2,
  63        .channels_max =     8,
  64        .buffer_bytes_max = PCM_BUFFER_MAX,
  65        .period_bytes_min = FRAGMENT_SIZE_MIN,
  66        .period_bytes_max = PCM_BUFFER_MAX/2,
  67        .periods_min =      FRAGMENTS_MIN,
  68        .periods_max =      FRAGMENTS_MAX,
  69};
  70
  71static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
  72        struct snd_pcm_hw_params *params)
  73{
  74        size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
  75        snd_pcm_lib_malloc_pages(substream, size * 4);
  76
  77        return 0;
  78}
  79
  80static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
  81{
  82        snd_pcm_lib_free_pages(substream);
  83
  84        return 0;
  85}
  86
  87static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
  88{
  89        struct snd_pcm_runtime *runtime = substream->runtime;
  90        struct sport_device *sport = runtime->private_data;
  91        int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
  92
  93        fragsize_bytes /= runtime->channels;
  94        /* inflate the fragsize to match the dma width of SPORT */
  95        fragsize_bytes *= 8;
  96
  97        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  98                sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
  99                sport_config_tx_dma(sport, runtime->dma_area,
 100                        runtime->periods, fragsize_bytes);
 101        } else {
 102                sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
 103                sport_config_rx_dma(sport, runtime->dma_area,
 104                        runtime->periods, fragsize_bytes);
 105        }
 106
 107        return 0;
 108}
 109
 110static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 111{
 112        struct snd_pcm_runtime *runtime = substream->runtime;
 113        struct sport_device *sport = runtime->private_data;
 114        int ret = 0;
 115
 116        switch (cmd) {
 117        case SNDRV_PCM_TRIGGER_START:
 118                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 119                        sport_tx_start(sport);
 120                else
 121                        sport_rx_start(sport);
 122                break;
 123        case SNDRV_PCM_TRIGGER_STOP:
 124        case SNDRV_PCM_TRIGGER_SUSPEND:
 125        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 126                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 127                        sport_tx_stop(sport);
 128                else
 129                        sport_rx_stop(sport);
 130                break;
 131        default:
 132                ret = -EINVAL;
 133        }
 134
 135        return ret;
 136}
 137
 138static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
 139{
 140        struct snd_pcm_runtime *runtime = substream->runtime;
 141        struct sport_device *sport = runtime->private_data;
 142        unsigned int diff;
 143        snd_pcm_uframes_t frames;
 144
 145        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 146                diff = sport_curr_offset_tx(sport);
 147                frames = diff / (8*4); /* 32 bytes per frame */
 148        } else {
 149                diff = sport_curr_offset_rx(sport);
 150                frames = diff / (8*4);
 151        }
 152        return frames;
 153}
 154
 155static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
 156{
 157        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 158        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 159        struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
 160        struct snd_pcm_runtime *runtime = substream->runtime;
 161        struct snd_dma_buffer *buf = &substream->dma_buffer;
 162
 163        int ret = 0;
 164
 165        snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
 166
 167        ret = snd_pcm_hw_constraint_integer(runtime,
 168                SNDRV_PCM_HW_PARAM_PERIODS);
 169        if (ret < 0)
 170                goto out;
 171
 172        if (sport_handle != NULL) {
 173                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 174                        sport_handle->tx_buf = buf->area;
 175                else
 176                        sport_handle->rx_buf = buf->area;
 177
 178                runtime->private_data = sport_handle;
 179        } else {
 180                pr_err("sport_handle is NULL\n");
 181                ret = -ENODEV;
 182        }
 183out:
 184        return ret;
 185}
 186
 187static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
 188        snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
 189{
 190        struct snd_pcm_runtime *runtime = substream->runtime;
 191        struct sport_device *sport = runtime->private_data;
 192        struct bf5xx_tdm_port *tdm_port = sport->private_data;
 193        unsigned int *src;
 194        unsigned int *dst;
 195        int i;
 196
 197        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 198                src = buf;
 199                dst = (unsigned int *)substream->runtime->dma_area;
 200
 201                dst += pos * 8;
 202                while (count--) {
 203                        for (i = 0; i < substream->runtime->channels; i++)
 204                                *(dst + tdm_port->tx_map[i]) = *src++;
 205                        dst += 8;
 206                }
 207        } else {
 208                src = (unsigned int *)substream->runtime->dma_area;
 209                dst = buf;
 210
 211                src += pos * 8;
 212                while (count--) {
 213                        for (i = 0; i < substream->runtime->channels; i++)
 214                                *dst++ = *(src + tdm_port->rx_map[i]);
 215                        src += 8;
 216                }
 217        }
 218
 219        return 0;
 220}
 221
 222static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
 223        int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
 224{
 225        unsigned char *buf = substream->runtime->dma_area;
 226        buf += pos * 8 * 4;
 227        memset(buf, '\0', count * 8 * 4);
 228
 229        return 0;
 230}
 231
 232
 233struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
 234        .open           = bf5xx_pcm_open,
 235        .ioctl          = snd_pcm_lib_ioctl,
 236        .hw_params      = bf5xx_pcm_hw_params,
 237        .hw_free        = bf5xx_pcm_hw_free,
 238        .prepare        = bf5xx_pcm_prepare,
 239        .trigger        = bf5xx_pcm_trigger,
 240        .pointer        = bf5xx_pcm_pointer,
 241        .copy           = bf5xx_pcm_copy,
 242        .silence        = bf5xx_pcm_silence,
 243};
 244
 245static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
 246{
 247        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 248        struct snd_dma_buffer *buf = &substream->dma_buffer;
 249        size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
 250
 251        buf->dev.type = SNDRV_DMA_TYPE_DEV;
 252        buf->dev.dev = pcm->card->dev;
 253        buf->private_data = NULL;
 254        buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
 255                &buf->addr, GFP_KERNEL);
 256        if (!buf->area) {
 257                pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
 258                return -ENOMEM;
 259        }
 260        buf->bytes = size;
 261
 262        return 0;
 263}
 264
 265static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
 266{
 267        struct snd_pcm_substream *substream;
 268        struct snd_dma_buffer *buf;
 269        int stream;
 270
 271        for (stream = 0; stream < 2; stream++) {
 272                substream = pcm->streams[stream].substream;
 273                if (!substream)
 274                        continue;
 275
 276                buf = &substream->dma_buffer;
 277                if (!buf->area)
 278                        continue;
 279                dma_free_coherent(NULL, buf->bytes, buf->area, 0);
 280                buf->area = NULL;
 281        }
 282}
 283
 284static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
 285
 286static int bf5xx_pcm_tdm_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 = 0;
 291
 292        if (!card->dev->dma_mask)
 293                card->dev->dma_mask = &bf5xx_pcm_dmamask;
 294        if (!card->dev->coherent_dma_mask)
 295                card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
 296
 297        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
 298                ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
 299                        SNDRV_PCM_STREAM_PLAYBACK);
 300                if (ret)
 301                        goto out;
 302        }
 303
 304        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
 305                ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
 306                        SNDRV_PCM_STREAM_CAPTURE);
 307                if (ret)
 308                        goto out;
 309        }
 310out:
 311        return ret;
 312}
 313
 314static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = {
 315        .ops        = &bf5xx_pcm_tdm_ops,
 316        .pcm_new        = bf5xx_pcm_tdm_new,
 317        .pcm_free       = bf5xx_pcm_free_dma_buffers,
 318};
 319
 320static int bf5xx_soc_platform_probe(struct platform_device *pdev)
 321{
 322        return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
 323}
 324
 325static int bf5xx_soc_platform_remove(struct platform_device *pdev)
 326{
 327        snd_soc_unregister_platform(&pdev->dev);
 328        return 0;
 329}
 330
 331static struct platform_driver bfin_tdm_driver = {
 332        .driver = {
 333                        .name = "bfin-tdm-pcm-audio",
 334                        .owner = THIS_MODULE,
 335        },
 336
 337        .probe = bf5xx_soc_platform_probe,
 338        .remove = bf5xx_soc_platform_remove,
 339};
 340
 341module_platform_driver(bfin_tdm_driver);
 342
 343MODULE_AUTHOR("Barry Song");
 344MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
 345MODULE_LICENSE("GPL");
 346