[funini.com] -> [kei@sodan] -> Kernel Reading

root/sound/soc/omap/omap-pcm.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. omap_pcm_dma_irq
  2. omap_pcm_hw_params
  3. omap_pcm_hw_free
  4. omap_pcm_prepare
  5. omap_pcm_trigger
  6. omap_pcm_pointer
  7. omap_pcm_open
  8. omap_pcm_close
  9. omap_pcm_mmap
  10. omap_pcm_preallocate_dma_buffer
  11. omap_pcm_free_dma_buffers
  12. omap_pcm_new

/*
 * omap-pcm.c  --  ALSA PCM interface for the OMAP SoC
 *
 * Copyright (C) 2008 Nokia Corporation
 *
 * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

#include <mach/dma.h>
#include "omap-pcm.h"

static const struct snd_pcm_hardware omap_pcm_hardware = {
        .info                   = SNDRV_PCM_INFO_MMAP |
                                  SNDRV_PCM_INFO_MMAP_VALID |
                                  SNDRV_PCM_INFO_INTERLEAVED |
                                  SNDRV_PCM_INFO_PAUSE |
                                  SNDRV_PCM_INFO_RESUME,
        .formats                = SNDRV_PCM_FMTBIT_S16_LE,
        .period_bytes_min       = 32,
        .period_bytes_max       = 64 * 1024,
        .periods_min            = 2,
        .periods_max            = 255,
        .buffer_bytes_max       = 128 * 1024,
};

struct omap_runtime_data {
        spinlock_t                      lock;
        struct omap_pcm_dma_data        *dma_data;
        int                             dma_ch;
        int                             period_index;
};

static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
{
        struct snd_pcm_substream *substream = data;
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd = runtime->private_data;
        unsigned long flags;

        if (cpu_is_omap1510()) {
                /*
                 * OMAP1510 doesn't support DMA chaining so have to restart
                 * the transfer after all periods are transferred
                 */
                spin_lock_irqsave(&prtd->lock, flags);
                if (prtd->period_index >= 0) {
                        if (++prtd->period_index == runtime->periods) {
                                prtd->period_index = 0;
                                omap_start_dma(prtd->dma_ch);
                        }
                }
                spin_unlock_irqrestore(&prtd->lock, flags);
        }

        snd_pcm_period_elapsed(substream);
}

/* this may get called several times by oss emulation */
static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct omap_runtime_data *prtd = runtime->private_data;
        struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data;
        int err = 0;

        if (!dma_data)
                return -ENODEV;

        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
        runtime->dma_bytes = params_buffer_bytes(params);

        if (prtd->dma_data)
                return 0;
        prtd->dma_data = dma_data;
        err = omap_request_dma(dma_data->dma_req, dma_data->name,
                               omap_pcm_dma_irq, substream, &prtd->dma_ch);
        if (!err & !cpu_is_omap1510()) {
                /*
                 * Link channel with itself so DMA doesn't need any
                 * reprogramming while looping the buffer
                 */
                omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
        }

        return err;
}

static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd = runtime->private_data;

        if (prtd->dma_data == NULL)
                return 0;

        if (!cpu_is_omap1510())
                omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
        omap_free_dma(prtd->dma_ch);
        prtd->dma_data = NULL;

        snd_pcm_set_runtime_buffer(substream, NULL);

        return 0;
}

static int omap_pcm_prepare(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd = runtime->private_data;
        struct omap_pcm_dma_data *dma_data = prtd->dma_data;
        struct omap_dma_channel_params dma_params;

        memset(&dma_params, 0, sizeof(dma_params));
        /*
         * Note: Regardless of interface data formats supported by OMAP McBSP
         * or EAC blocks, internal representation is always fixed 16-bit/sample
         */
        dma_params.data_type                    = OMAP_DMA_DATA_TYPE_S16;
        dma_params.trigger                      = dma_data->dma_req;
        dma_params.sync_mode                    = OMAP_DMA_SYNC_ELEMENT;
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
                dma_params.src_amode            = OMAP_DMA_AMODE_POST_INC;
                dma_params.dst_amode            = OMAP_DMA_AMODE_CONSTANT;
                dma_params.src_or_dst_synch     = OMAP_DMA_DST_SYNC;
                dma_params.src_start            = runtime->dma_addr;
                dma_params.dst_start            = dma_data->port_addr;
                dma_params.dst_port             = OMAP_DMA_PORT_MPUI;
        } else {
                dma_params.src_amode            = OMAP_DMA_AMODE_CONSTANT;
                dma_params.dst_amode            = OMAP_DMA_AMODE_POST_INC;
                dma_params.src_or_dst_synch     = OMAP_DMA_SRC_SYNC;
                dma_params.src_start            = dma_data->port_addr;
                dma_params.dst_start            = runtime->dma_addr;
                dma_params.src_port             = OMAP_DMA_PORT_MPUI;
        }
        /*
         * Set DMA transfer frame size equal to ALSA period size and frame
         * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
         * we can transfer the whole ALSA buffer with single DMA transfer but
         * still can get an interrupt at each period bounary
         */
        dma_params.elem_count   = snd_pcm_lib_period_bytes(substream) / 2;
        dma_params.frame_count  = runtime->periods;
        omap_set_dma_params(prtd->dma_ch, &dma_params);

        omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);

        return 0;
}

static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd = runtime->private_data;
        int ret = 0;

        spin_lock_irq(&prtd->lock);
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_RESUME:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                prtd->period_index = 0;
                omap_start_dma(prtd->dma_ch);
                break;

        case SNDRV_PCM_TRIGGER_STOP:
        case SNDRV_PCM_TRIGGER_SUSPEND:
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
                prtd->period_index = -1;
                omap_stop_dma(prtd->dma_ch);
                break;
        default:
                ret = -EINVAL;
        }
        spin_unlock_irq(&prtd->lock);

        return ret;
}

static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd = runtime->private_data;
        dma_addr_t ptr;
        snd_pcm_uframes_t offset;

        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                ptr = omap_get_dma_src_pos(prtd->dma_ch);
        else
                ptr = omap_get_dma_dst_pos(prtd->dma_ch);

        offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
        if (offset >= runtime->buffer_size)
                offset = 0;

        return offset;
}

static int omap_pcm_open(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct omap_runtime_data *prtd;
        int ret;

        snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);

        /* Ensure that buffer size is a multiple of period size */
        ret = snd_pcm_hw_constraint_integer(runtime,
                                            SNDRV_PCM_HW_PARAM_PERIODS);
        if (ret < 0)
                goto out;

        prtd = kzalloc(sizeof(prtd), GFP_KERNEL);
        if (prtd == NULL) {
                ret = -ENOMEM;
                goto out;
        }
        spin_lock_init(&prtd->lock);
        runtime->private_data = prtd;

out:
        return ret;
}

static int omap_pcm_close(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;

        kfree(runtime->private_data);
        return 0;
}

static int omap_pcm_mmap(struct snd_pcm_substream *substream,
        struct vm_area_struct *vma)
{
        struct snd_pcm_runtime *runtime = substream->runtime;

        return dma_mmap_writecombine(substream->pcm->card->dev, vma,
                                     runtime->dma_area,
                                     runtime->dma_addr,
                                     runtime->dma_bytes);
}

struct snd_pcm_ops omap_pcm_ops = {
        .open           = omap_pcm_open,
        .close          = omap_pcm_close,
        .ioctl          = snd_pcm_lib_ioctl,
        .hw_params      = omap_pcm_hw_params,
        .hw_free        = omap_pcm_hw_free,
        .prepare        = omap_pcm_prepare,
        .trigger        = omap_pcm_trigger,
        .pointer        = omap_pcm_pointer,
        .mmap           = omap_pcm_mmap,
};

static u64 omap_pcm_dmamask = DMA_BIT_MASK(32);

static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
        int stream)
{
        struct snd_pcm_substream *substream = pcm->streams[stream].substream;
        struct snd_dma_buffer *buf = &substream->dma_buffer;
        size_t size = omap_pcm_hardware.buffer_bytes_max;

        buf->dev.type = SNDRV_DMA_TYPE_DEV;
        buf->dev.dev = pcm->card->dev;
        buf->private_data = NULL;
        buf->area = dma_alloc_writecombine(pcm->card->dev, size,
                                           &buf->addr, GFP_KERNEL);
        if (!buf->area)
                return -ENOMEM;

        buf->bytes = size;
        return 0;
}

static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
        struct snd_pcm_substream *substream;
        struct snd_dma_buffer *buf;
        int stream;

        for (stream = 0; stream < 2; stream++) {
                substream = pcm->streams[stream].substream;
                if (!substream)
                        continue;

                buf = &substream->dma_buffer;
                if (!buf->area)
                        continue;

                dma_free_writecombine(pcm->card->dev, buf->bytes,
                                      buf->area, buf->addr);
                buf->area = NULL;
        }
}

int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
                 struct snd_pcm *pcm)
{
        int ret = 0;

        if (!card->dev->dma_mask)
                card->dev->dma_mask = &omap_pcm_dmamask;
        if (!card->dev->coherent_dma_mask)
                card->dev->coherent_dma_mask = DMA_32BIT_MASK;

        if (dai->playback.channels_min) {
                ret = omap_pcm_preallocate_dma_buffer(pcm,
                        SNDRV_PCM_STREAM_PLAYBACK);
                if (ret)
                        goto out;
        }

        if (dai->capture.channels_min) {
                ret = omap_pcm_preallocate_dma_buffer(pcm,
                        SNDRV_PCM_STREAM_CAPTURE);
                if (ret)
                        goto out;
        }

out:
        return ret;
}

struct snd_soc_platform omap_soc_platform = {
        .name           = "omap-pcm-audio",
        .pcm_ops        = &omap_pcm_ops,
        .pcm_new        = omap_pcm_new,
        .pcm_free       = omap_pcm_free_dma_buffers,
};
EXPORT_SYMBOL_GPL(omap_soc_platform);

MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
MODULE_DESCRIPTION("OMAP PCM DMA module");
MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */

[funini.com] -> [kei@sodan] -> Kernel Reading