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");