Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/sound/soc/msm/msm8660-pcm.c b/sound/soc/msm/msm8660-pcm.c
new file mode 100644
index 0000000..6f6fe43
--- /dev/null
+++ b/sound/soc/msm/msm8660-pcm.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/audio_dma_msm8k.h>
+#include <sound/dai.h>
+#include "msm8660-pcm.h"
+
+static const struct snd_pcm_hardware msm_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = DMASZ/4,
+ .buffer_bytes_max = DMASZ,
+ .rate_max = 96000,
+ .rate_min = 8000,
+ .channels_min = USE_CHANNELS_MIN,
+ .channels_max = USE_CHANNELS_MAX,
+ .periods_min = 4,
+ .periods_max = 512,
+ .fifo_size = 0,
+};
+
+struct msm_pcm_data {
+ spinlock_t lock;
+ int ch;
+};
+
+/* Conventional and unconventional sample rate supported */
+static unsigned int supported_sample_rates[] = {
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
+ .count = ARRAY_SIZE(supported_sample_rates),
+ .list = supported_sample_rates,
+ .mask = 0,
+};
+
+static int msm_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+
+ pr_debug("%s\n", __func__);
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ return 0;
+}
+
+static irqreturn_t msm_pcm_irq(int intrsrc, void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msm_audio *prtd = (struct msm_audio *)runtime->private_data;
+ int dma_ch = 0;
+ unsigned int has_xrun, pending;
+ int ret = IRQ_NONE;
+
+ if (prtd)
+ dma_ch = prtd->dma_ch;
+ else
+ return ret;
+
+ pr_debug("msm8660-pcm: msm_pcm_irq called\n");
+ pending = (intrsrc
+ & (UNDER_CH(dma_ch) | PER_CH(dma_ch) | ERR_CH(dma_ch)));
+ has_xrun = (pending & UNDER_CH(dma_ch));
+
+ if (unlikely(has_xrun) &&
+ substream->runtime &&
+ snd_pcm_running(substream)) {
+ pr_err("xrun\n");
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+ ret = IRQ_HANDLED;
+ pending &= ~UNDER_CH(dma_ch);
+ }
+
+
+ if (pending & PER_CH(dma_ch)) {
+ ret = IRQ_HANDLED;
+ if (likely(substream->runtime &&
+ snd_pcm_running(substream))) {
+ /* end of buffer missed? loop back */
+ if (++prtd->period_index >= runtime->periods)
+ prtd->period_index = 0;
+ snd_pcm_period_elapsed(substream);
+ pr_debug("period elapsed\n");
+ }
+ pending &= ~PER_CH(dma_ch);
+ }
+
+ if (unlikely(pending
+ & (UNDER_CH(dma_ch) & PER_CH(dma_ch) & ERR_CH(dma_ch)))) {
+ if (pending & UNDER_CH(dma_ch))
+ pr_err("msm8660-pcm: DMA %x Underflow\n",
+ dma_ch);
+ if (pending & ERR_CH(dma_ch))
+ pr_err("msm8660-pcm: DMA %x Master Error\n",
+ dma_ch);
+
+ }
+ return ret;
+}
+
+static int msm_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msm_audio *prtd = (struct msm_audio *)runtime->private_data;
+ struct dai_dma_params dma_params;
+ int dma_ch = 0;
+
+ if (prtd)
+ dma_ch = prtd->dma_ch;
+ else
+ return 0;
+
+ prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream);
+ prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
+ pr_debug("%s:prtd->pcm_size = %d\n", __func__, prtd->pcm_size);
+ pr_debug("%s:prtd->pcm_count = %d\n", __func__, prtd->pcm_count);
+
+ if (prtd->enabled)
+ return 0;
+
+ dma_params.src_start = runtime->dma_addr;
+ dma_params.buffer = (u8 *)runtime->dma_area;
+ dma_params.buffer_size = prtd->pcm_size;
+ dma_params.period_size = prtd->pcm_count;
+ dma_params.channels = runtime->channels;
+
+ dai_set_params(dma_ch, &dma_params);
+ register_dma_irq_handler(dma_ch, msm_pcm_irq, (void *)substream);
+
+ prtd->enabled = 1;
+ return 0;
+}
+
+static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msm_audio *prtd = (struct msm_audio *)runtime->private_data;
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ dai_start(prtd->dma_ch);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dai_stop(prtd->dma_ch);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msm_audio *prtd = (struct msm_audio *)runtime->private_data;
+ snd_pcm_uframes_t offset = 0;
+
+ pr_debug("%s: period_index =%d\n", __func__, prtd->period_index);
+ offset = prtd->period_index * runtime->period_size;
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ return offset;
+}
+
+static int msm_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct msm_audio *prtd = NULL;
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+ snd_soc_set_runtime_hwparams(substream, &msm_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ if (ret < 0) {
+ pr_err("Error setting hw_constraint\n");
+ goto err;
+ }
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_sample_rates);
+ if (ret < 0)
+ pr_err("Error snd_pcm_hw_constraint_list failed\n");
+
+ prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL);
+
+ if (prtd == NULL) {
+ pr_err("Error allocating prtd\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+ prtd->dma_ch = cpu_dai->id;
+ prtd->enabled = 0;
+ runtime->dma_bytes = msm_pcm_hardware.buffer_bytes_max;
+ runtime->private_data = prtd;
+err:
+ return ret;
+}
+
+static int msm_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msm_audio *prtd = (struct msm_audio *)runtime->private_data;
+ int dma_ch = 0;
+
+ if (prtd)
+ dma_ch = prtd->dma_ch;
+ else
+ return 0;
+
+ pr_debug("%s\n", __func__);
+ unregister_dma_irq_handler(dma_ch);
+ kfree(runtime->private_data);
+ return 0;
+}
+
+static int msm_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vms)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ pr_debug("%s\n", __func__);
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ pr_debug("%s: snd_msm_audio_hw_params runtime->dma_addr 0x(%x)\n",
+ __func__, (unsigned int)runtime->dma_addr);
+ pr_debug("%s: snd_msm_audio_hw_params runtime->dma_area 0x(%x)\n",
+ __func__, (unsigned int)runtime->dma_area);
+ pr_debug("%s: snd_msm_audio_hw_params runtime->dma_bytes 0x(%x)\n",
+ __func__, (unsigned int)runtime->dma_bytes);
+
+ return dma_mmap_coherent(substream->pcm->card->dev, vms,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+
+static struct snd_pcm_ops msm_pcm_ops = {
+ .open = msm_pcm_open,
+ .close = msm_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = msm_pcm_hw_params,
+ .prepare = msm_pcm_prepare,
+ .trigger = msm_pcm_trigger,
+ .pointer = msm_pcm_pointer,
+ .mmap = msm_pcm_mmap,
+};
+
+static int pcm_preallocate_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 = msm_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_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void msm_pcm_free_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 (!stream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+static u64 msm_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int msm_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 = &msm_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->playback.channels_min) {
+ ret = pcm_preallocate_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+ if (dai->capture.channels_min) {
+ ret = pcm_preallocate_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+struct snd_soc_platform msm8660_soc_platform = {
+ .name = "msm8660-pcm-audio",
+ .pcm_ops = &msm_pcm_ops,
+ .pcm_new = msm_pcm_new,
+ .pcm_free = msm_pcm_free_buffers,
+};
+EXPORT_SYMBOL_GPL(msm8660_soc_platform);
+
+static int __init msm_soc_platform_init(void)
+{
+ return snd_soc_register_platform(&msm8660_soc_platform);
+}
+static void __exit msm_soc_platform_exit(void)
+{
+ snd_soc_unregister_platform(&msm8660_soc_platform);
+}
+module_init(msm_soc_platform_init);
+module_exit(msm_soc_platform_exit);
+
+MODULE_DESCRIPTION("MSM PCM module");
+MODULE_LICENSE("GPL v2");