android_kernel_google_msm/sound/soc/msm/msm-pcm-afe.c
Asish Bhattacharya 119570c698 ASoC: msm: Fix issues in proxy port driver due to early afe port enable.
After recent movement of afe_start to prepare the interrupts start
coming earlier from proxy port.
This results in proxy port driver not ready for interrupts. Start the
timer later to ensure processing of packet once trigger start happens.

Change-Id: Ie82d982b6c147afdef46a8c7bfe7234dd14486f1
Signed-off-by: Asish Bhattacharya <asishb@codeaurora.org>
2013-02-27 18:19:08 -08:00

619 lines
16 KiB
C

/* Copyright (c) 2011-2012, 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/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/q6adm.h>
#include <asm/dma.h>
#include <linux/memory_alloc.h>
#include <mach/msm_subsystem_map.h>
#include "msm-pcm-afe.h"
#include "msm-pcm-q6.h"
#define MIN_PERIOD_SIZE (128 * 2)
#define MAX_PERIOD_SIZE (128 * 2 * 2 * 6)
static struct snd_pcm_hardware msm_afe_hardware = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = MAX_PERIOD_SIZE * 32,
.period_bytes_min = MIN_PERIOD_SIZE,
.period_bytes_max = MAX_PERIOD_SIZE,
.periods_min = 32,
.periods_max = 384,
.fifo_size = 0,
};
static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt);
static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt);
static void q6asm_event_handler(uint32_t opcode,
uint32_t token, uint32_t *payload, void *priv)
{
}
static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt)
{
struct pcm_afe_info *prtd =
container_of(hrt, struct pcm_afe_info, hrt);
struct snd_pcm_substream *substream = prtd->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
if (prtd->start) {
pr_debug("sending frame to DSP: poll_time: %d\n",
prtd->poll_time);
if (prtd->dsp_cnt == runtime->periods)
prtd->dsp_cnt = 0;
afe_rt_proxy_port_write(
(prtd->dma_addr +
(prtd->dsp_cnt *
snd_pcm_lib_period_bytes(prtd->substream))),
snd_pcm_lib_period_bytes(prtd->substream));
prtd->dsp_cnt++;
hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time
* 1000));
return HRTIMER_RESTART;
} else
return HRTIMER_NORESTART;
}
static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt)
{
struct pcm_afe_info *prtd =
container_of(hrt, struct pcm_afe_info, hrt);
struct snd_pcm_substream *substream = prtd->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
if (prtd->start) {
if (prtd->dsp_cnt == runtime->periods)
prtd->dsp_cnt = 0;
afe_rt_proxy_port_read(
(prtd->dma_addr + (prtd->dsp_cnt
* snd_pcm_lib_period_bytes(prtd->substream))),
snd_pcm_lib_period_bytes(prtd->substream));
prtd->dsp_cnt++;
pr_debug("sending frame rec to DSP: poll_time: %d\n",
prtd->poll_time);
hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time
* 1000));
return HRTIMER_RESTART;
} else
return HRTIMER_NORESTART;
}
static void pcm_afe_process_tx_pkt(uint32_t opcode,
uint32_t token, uint32_t *payload,
void *priv)
{
struct pcm_afe_info *prtd = priv;
unsigned long dsp_flags;
struct snd_pcm_substream *substream = NULL;
struct snd_pcm_runtime *runtime = NULL;
uint16_t event;
if (prtd == NULL)
return;
substream = prtd->substream;
runtime = substream->runtime;
pr_debug("%s\n", __func__);
spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
switch (opcode) {
case AFE_EVENT_RT_PROXY_PORT_STATUS: {
event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10);
switch (event) {
case AFE_EVENT_RTPORT_START: {
prtd->dsp_cnt = 0;
prtd->poll_time = ((unsigned long)((
snd_pcm_lib_period_bytes
(prtd->substream) *
1000 * 1000)/
(runtime->rate *
runtime->channels * 2)));
pr_debug("prtd->poll_time: %d",
prtd->poll_time);
break;
}
case AFE_EVENT_RTPORT_STOP:
pr_debug("%s: event!=0\n", __func__);
prtd->start = 0;
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
break;
case AFE_EVENT_RTPORT_LOW_WM:
pr_debug("%s: Underrun\n", __func__);
break;
case AFE_EVENT_RTPORT_HI_WM:
pr_debug("%s: Overrun\n", __func__);
break;
default:
break;
}
break;
}
case APR_BASIC_RSP_RESULT: {
switch (payload[0]) {
case AFE_SERVICE_CMD_RTPORT_WR:
pr_debug("write done\n");
prtd->pcm_irq_pos += snd_pcm_lib_period_bytes
(prtd->substream);
snd_pcm_period_elapsed(prtd->substream);
break;
default:
break;
}
break;
}
default:
break;
}
spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
}
static void pcm_afe_process_rx_pkt(uint32_t opcode,
uint32_t token, uint32_t *payload,
void *priv)
{
struct pcm_afe_info *prtd = priv;
unsigned long dsp_flags;
struct snd_pcm_substream *substream = NULL;
struct snd_pcm_runtime *runtime = NULL;
uint16_t event;
if (prtd == NULL)
return;
substream = prtd->substream;
runtime = substream->runtime;
pr_debug("%s\n", __func__);
spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
switch (opcode) {
case AFE_EVENT_RT_PROXY_PORT_STATUS: {
event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10);
switch (event) {
case AFE_EVENT_RTPORT_START: {
prtd->dsp_cnt = 0;
prtd->poll_time = ((unsigned long)((
snd_pcm_lib_period_bytes(prtd->substream)
* 1000 * 1000)/(runtime->rate
* runtime->channels * 2)));
pr_debug("prtd->poll_time : %d", prtd->poll_time);
break;
}
case AFE_EVENT_RTPORT_STOP:
pr_debug("%s: event!=0\n", __func__);
prtd->start = 0;
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
break;
case AFE_EVENT_RTPORT_LOW_WM:
pr_debug("%s: Underrun\n", __func__);
break;
case AFE_EVENT_RTPORT_HI_WM:
pr_debug("%s: Overrun\n", __func__);
break;
default:
break;
}
break;
}
case APR_BASIC_RSP_RESULT: {
switch (payload[0]) {
case AFE_SERVICE_CMD_RTPORT_RD:
pr_debug("Read done\n");
prtd->pcm_irq_pos += snd_pcm_lib_period_bytes
(prtd->substream);
snd_pcm_period_elapsed(prtd->substream);
break;
default:
break;
}
break;
}
default:
break;
}
spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
}
static int msm_afe_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *dai = rtd->cpu_dai;
int ret = 0;
pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate);
pr_debug("%s: dai->id =%x\n", __func__, dai->id);
ret = afe_register_get_events(dai->id,
pcm_afe_process_tx_pkt, prtd);
if (ret < 0) {
pr_err("afe-pcm:register for events failed\n");
return ret;
}
pr_debug("%s:success\n", __func__);
prtd->prepared++;
return ret;
}
static int msm_afe_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *dai = rtd->cpu_dai;
int ret = 0;
pr_debug("%s\n", __func__);
pr_debug("%s: dai->id =%x\n", __func__, dai->id);
ret = afe_register_get_events(dai->id,
pcm_afe_process_rx_pkt, prtd);
if (ret < 0) {
pr_err("afe-pcm:register for events failed\n");
return ret;
}
pr_debug("%s:success\n", __func__);
prtd->prepared++;
return 0;
}
/* Conventional and unconventional sample rate supported */
static unsigned int supported_sample_rates[] = {
8000, 16000, 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_afe_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = NULL;
int ret = 0;
prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL);
if (prtd == NULL) {
pr_err("Failed to allocate memory for msm_audio\n");
return -ENOMEM;
} else
pr_debug("prtd %x\n", (unsigned int)prtd);
mutex_init(&prtd->lock);
spin_lock_init(&prtd->dsp_lock);
prtd->dsp_cnt = 0;
mutex_lock(&prtd->lock);
runtime->hw = msm_afe_hardware;
prtd->substream = substream;
runtime->private_data = prtd;
prtd->audio_client = q6asm_audio_client_alloc(
(app_cb)q6asm_event_handler, prtd);
if (!prtd->audio_client) {
pr_debug("%s: Could not allocate memory\n", __func__);
kfree(prtd);
mutex_unlock(&prtd->lock);
return -ENOMEM;
}
hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
prtd->hrt.function = afe_hrtimer_callback;
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
prtd->hrt.function = afe_hrtimer_rec_callback;
mutex_unlock(&prtd->lock);
ret = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
if (ret < 0)
pr_err("snd_pcm_hw_constraint_list failed\n");
/* 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)
pr_err("snd_pcm_hw_constraint_integer failed\n");
return 0;
}
static int msm_afe_close(struct snd_pcm_substream *substream)
{
int rc = 0;
struct snd_dma_buffer *dma_buf;
struct snd_pcm_runtime *runtime;
struct pcm_afe_info *prtd;
struct snd_soc_pcm_runtime *rtd = NULL;
struct snd_soc_dai *dai = NULL;
int dir = IN;
int ret = 0;
pr_debug("%s\n", __func__);
if (substream == NULL) {
pr_err("substream is NULL\n");
return -EINVAL;
}
rtd = substream->private_data;
dai = rtd->cpu_dai;
runtime = substream->runtime;
prtd = runtime->private_data;
mutex_lock(&prtd->lock);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dir = IN;
ret = afe_unregister_get_events(dai->id);
if (ret < 0)
pr_err("AFE unregister for events failed\n");
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
dir = OUT;
ret = afe_unregister_get_events(dai->id);
if (ret < 0)
pr_err("AFE unregister for events failed\n");
}
hrtimer_cancel(&prtd->hrt);
rc = afe_cmd_memory_unmap(runtime->dma_addr);
if (rc < 0)
pr_err("AFE memory unmap failed\n");
pr_debug("release all buffer\n");
dma_buf = &substream->dma_buffer;
if (dma_buf == NULL) {
pr_debug("dma_buf is NULL\n");
goto done;
}
if (dma_buf->area) {
dma_buf->area = NULL;
}
q6asm_audio_client_buf_free_contiguous(dir,
prtd->audio_client);
done:
pr_debug("%s: dai->id =%x\n", __func__, dai->id);
q6asm_audio_client_free(prtd->audio_client);
mutex_unlock(&prtd->lock);
prtd->prepared--;
kfree(prtd);
return 0;
}
static int msm_afe_prepare(struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
prtd->pcm_irq_pos = 0;
if (prtd->prepared)
return 0;
mutex_lock(&prtd->lock);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = msm_afe_playback_prepare(substream);
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ret = msm_afe_capture_prepare(substream);
mutex_unlock(&prtd->lock);
return ret;
}
static int msm_afe_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
int result = 0;
pr_debug("%s\n", __func__);
prtd->mmap_flag = 1;
if (runtime->dma_addr && runtime->dma_bytes) {
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
result = remap_pfn_range(vma, vma->vm_start,
runtime->dma_addr >> PAGE_SHIFT,
runtime->dma_bytes,
vma->vm_page_prot);
} else {
pr_err("Physical address or size of buf is NULL");
return -EINVAL;
}
return result;
}
static int msm_afe_trigger(struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pr_debug("%s: SNDRV_PCM_TRIGGER_START\n", __func__);
prtd->start = 1;
hrtimer_start(&prtd->hrt, ns_to_ktime(0),
HRTIMER_MODE_REL);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
prtd->start = 0;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int msm_afe_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dma_buffer *dma_buf = &substream->dma_buffer;
struct pcm_afe_info *prtd = runtime->private_data;
struct audio_buffer *buf;
int dir, ret;
pr_debug("%s:\n", __func__);
mutex_lock(&prtd->lock);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = IN;
else
dir = OUT;
ret = q6asm_audio_client_buf_alloc_contiguous(dir,
prtd->audio_client,
runtime->hw.period_bytes_min,
runtime->hw.periods_max);
if (ret < 0) {
pr_err("Audio Start: Buffer Allocation failed rc = %d\n", ret);
mutex_unlock(&prtd->lock);
return -ENOMEM;
}
buf = prtd->audio_client->port[dir].buf;
if (buf == NULL || buf[0].data == NULL) {
mutex_unlock(&prtd->lock);
return -ENOMEM;
}
pr_debug("%s:buf = %p\n", __func__, buf);
dma_buf->dev.type = SNDRV_DMA_TYPE_DEV;
dma_buf->dev.dev = substream->pcm->card->dev;
dma_buf->private_data = NULL;
dma_buf->area = buf[0].data;
dma_buf->addr = buf[0].phys;
dma_buf->bytes = runtime->hw.buffer_bytes_max;
if (!dma_buf->area) {
pr_err("%s:MSM AFE physical memory allocation failed\n",
__func__);
mutex_unlock(&prtd->lock);
return -ENOMEM;
}
memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max);
prtd->dma_addr = (u32) dma_buf->addr;
mutex_unlock(&prtd->lock);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
ret = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes);
if (ret < 0)
pr_err("fail to map memory to DSP\n");
return ret;
}
static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcm_afe_info *prtd = runtime->private_data;
if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream))
prtd->pcm_irq_pos = 0;
pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos);
return bytes_to_frames(runtime, (prtd->pcm_irq_pos));
}
static struct snd_pcm_ops msm_afe_ops = {
.open = msm_afe_open,
.hw_params = msm_afe_hw_params,
.trigger = msm_afe_trigger,
.close = msm_afe_close,
.prepare = msm_afe_prepare,
.mmap = msm_afe_mmap,
.pointer = msm_afe_pointer,
};
static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
int ret = 0;
pr_debug("%s\n", __func__);
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
return ret;
}
static int msm_afe_afe_probe(struct snd_soc_platform *platform)
{
pr_debug("%s\n", __func__);
return 0;
}
static struct snd_soc_platform_driver msm_soc_platform = {
.ops = &msm_afe_ops,
.pcm_new = msm_asoc_pcm_new,
.probe = msm_afe_afe_probe,
};
static __devinit int msm_afe_probe(struct platform_device *pdev)
{
pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev));
return snd_soc_register_platform(&pdev->dev,
&msm_soc_platform);
}
static int msm_afe_remove(struct platform_device *pdev)
{
pr_debug("%s\n", __func__);
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver msm_afe_driver = {
.driver = {
.name = "msm-pcm-afe",
.owner = THIS_MODULE,
},
.probe = msm_afe_probe,
.remove = __devexit_p(msm_afe_remove),
};
static int __init msm_soc_platform_init(void)
{
pr_debug("%s\n", __func__);
return platform_driver_register(&msm_afe_driver);
}
module_init(msm_soc_platform_init);
static void __exit msm_soc_platform_exit(void)
{
pr_debug("%s\n", __func__);
platform_driver_unregister(&msm_afe_driver);
}
module_exit(msm_soc_platform_exit);
MODULE_DESCRIPTION("AFE PCM module platform driver");
MODULE_LICENSE("GPL v2");