blob: 880ca87e0c67a1808c5f1d31c7a5ad86536fe505 [file] [log] [blame]
/*
* Copyright (C) ST-Ericsson SA 2010
*
* Author: Ola Lilja ola.o.lilja@stericsson.com,
* Roger Nilsson roger.xr.nilsson@stericsson.com
* for ST-Ericsson.
*
* License terms:
*
* 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.
*/
#include <asm/page.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "ux500_pcm.h"
#include "ux500_msp_dai.h"
static struct snd_pcm_hardware ux500_pcm_hw_playback = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
.rates = SNDRV_PCM_RATE_KNOT,
.rate_min = UX500_PLATFORM_MIN_RATE_PLAYBACK,
.rate_max = UX500_PLATFORM_MAX_RATE_PLAYBACK,
.channels_min = UX500_PLATFORM_MIN_CHANNELS,
.channels_max = UX500_PLATFORM_MAX_CHANNELS,
.buffer_bytes_max = UX500_PLATFORM_BUFFER_SIZE,
.period_bytes_min = UX500_PLATFORM_MIN_PERIOD_BYTES,
.period_bytes_max = PAGE_SIZE,
.periods_min = UX500_PLATFORM_BUFFER_SIZE / PAGE_SIZE,
.periods_max = UX500_PLATFORM_BUFFER_SIZE /
UX500_PLATFORM_MIN_PERIOD_BYTES
};
static struct snd_pcm_hardware ux500_pcm_hw_capture = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
.rates = SNDRV_PCM_RATE_KNOT,
.rate_min = UX500_PLATFORM_MIN_RATE_CAPTURE,
.rate_max = UX500_PLATFORM_MAX_RATE_CAPTURE,
.channels_min = UX500_PLATFORM_MIN_CHANNELS,
.channels_max = UX500_PLATFORM_MAX_CHANNELS,
.buffer_bytes_max = UX500_PLATFORM_BUFFER_SIZE,
.period_bytes_min = UX500_PLATFORM_MIN_PERIOD_BYTES,
.period_bytes_max = PAGE_SIZE,
.periods_min = UX500_PLATFORM_BUFFER_SIZE / PAGE_SIZE,
.periods_max = UX500_PLATFORM_BUFFER_SIZE /
UX500_PLATFORM_MIN_PERIOD_BYTES
};
static void ux500_pcm_dma_start(struct snd_pcm_substream *substream)
{
unsigned int offset, dma_size;
struct snd_pcm_runtime *runtime = substream->runtime;
struct ux500_pcm_private_s *private = substream->runtime->private_data;
pr_debug("%s: Enter MSP Index: %d\n",
__func__,
private->msp_id);
dma_size = frames_to_bytes(runtime, runtime->period_size);
offset = dma_size * private->period;
private->old_offset = offset;
pr_debug("%s: address = %x size = %d\n",
__func__,
(runtime->dma_addr + offset), dma_size);
if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ux500_msp_dai_i2s_send_data(
(void *)(runtime->dma_addr + offset),
dma_size,
private->msp_id);
} else{
ux500_msp_dai_i2s_receive_data(
(void *)(runtime->dma_addr + offset),
dma_size,
private->msp_id);
}
private->period++;
private->period %= runtime->periods;
private->periods++;
}
static void ux500_pcm_dma_hw_free(struct device *dev,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dma_buffer *buf = runtime->dma_buffer_p;
if (runtime->dma_area == NULL)
return;
if (buf != &substream->dma_buffer) {
dma_free_coherent(
buf->dev.dev,
buf->bytes,
buf->area,
buf->addr);
kfree(runtime->dma_buffer_p);
}
snd_pcm_set_runtime_buffer(substream, NULL);
}
irqreturn_t ux500_pcm_dma_eot_handler(void *data, int irq)
{
struct snd_pcm_substream *substream = data;
struct ux500_pcm_private_s *private =
substream->runtime->private_data;
pr_debug("%s: Enter\n", __func__);
if (substream)
snd_pcm_period_elapsed(substream);
if (private->state == ALSA_STATE_PAUSED)
return IRQ_HANDLED;
if (private->active == 1)
ux500_pcm_dma_start(substream);
return IRQ_HANDLED;
}
EXPORT_SYMBOL(ux500_pcm_dma_eot_handler);
static int ux500_pcm_open(struct snd_pcm_substream *substream)
{
int stream_id = substream->pstr->stream;
struct snd_pcm_runtime *runtime = substream->runtime;
struct ux500_pcm_private_s *private;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
pr_debug("%s: Enter\n", __func__);
private = kzalloc(sizeof(struct ux500_pcm_private_s), GFP_KERNEL);
if (private == NULL)
return -ENOMEM;
init_MUTEX(&(private->alsa_sem));
init_completion(&(private->alsa_com));
private->state = ALSA_STATE_RUNNING;
private->active = 0;
private->msp_id = rtd->dai->cpu_dai->id;
runtime->private_data = private;
pr_debug("%s: Setting HW-config\n", __func__);
runtime->hw = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ?
ux500_pcm_hw_playback : ux500_pcm_hw_capture;
return 0;
}
static int ux500_pcm_close(struct snd_pcm_substream *substream)
{
struct ux500_pcm_private_s *private = substream->runtime->private_data;
pr_debug("%s: Enter\n", __func__);
kfree(private);
return 0;
}
static int ux500_pcm_hw_params(
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dma_buffer *buf = runtime->dma_buffer_p;
int ret = 0;
int size;
pr_debug("%s: Enter\n", __func__);
size = params_buffer_bytes(hw_params);
if (buf) {
if (buf->bytes >= size)
goto out;
ux500_pcm_dma_hw_free(NULL, substream);
}
if (substream->dma_buffer.area != NULL &&
substream->dma_buffer.bytes >= size) {
buf = &substream->dma_buffer;
} else {
buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL);
if (!buf)
goto nomem;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = NULL;
buf->area = dma_alloc_coherent(
NULL,
size,
&buf->addr,
GFP_KERNEL);
buf->bytes = size;
buf->private_data = NULL;
if (!buf->area)
goto free;
}
snd_pcm_set_runtime_buffer(substream, buf);
ret = 1;
out:
runtime->dma_bytes = size;
return ret;
free:
kfree(buf);
nomem:
return -ENOMEM;
}
static int ux500_pcm_hw_free(struct snd_pcm_substream *substream)
{
pr_debug("%s: Enter\n", __func__);
ux500_pcm_dma_hw_free(NULL, substream);
return 0;
}
static int ux500_pcm_prepare(struct snd_pcm_substream *substream)
{
pr_debug("%s: Enter\n", __func__);
return 0;
}
static int ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct ux500_pcm_private_s *private = substream->runtime->private_data;
pr_debug("%s: Enter\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
pr_debug("%s: TRIGGER START\n", __func__);
if (private->active == 0) {
private->active = 1;
ux500_pcm_dma_start(substream);
break;
}
pr_debug("%s: Error: H/w is busy\n", __func__);
return -EINVAL;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pr_debug("%s: SNDRV_PCM_TRIGGER_PAUSE\n", __func__);
if (private->state == ALSA_STATE_RUNNING)
private->state = ALSA_STATE_PAUSED;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pr_debug("%s: SNDRV_PCM_TRIGGER_RESUME\n", __func__);
if (private->state == ALSA_STATE_PAUSED) {
private->state = ALSA_STATE_RUNNING;
ux500_pcm_dma_start(substream);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
if (private->active == 1) {
private->active = 0;
private->period = 0;
}
break;
default:
pr_err("%s: Invalid command in pcm trigger\n",
__func__);
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t ux500_pcm_pointer(struct snd_pcm_substream *substream)
{
unsigned int offset;
struct snd_pcm_runtime *runtime = substream->runtime;
struct ux500_pcm_private_s *private = substream->runtime->private_data;
pr_debug("%s: Enter\n", __func__);
offset = bytes_to_frames(runtime, private->old_offset);
if (offset < 0 || private->old_offset < 0)
pr_debug("%s: Offset=%i %i\n",
__func__,
offset,
private->old_offset);
return offset;
}
static int ux500_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
pr_debug("%s: Enter.\n", __func__);
return dma_mmap_coherent(
NULL,
vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static struct snd_pcm_ops ux500_pcm_ops = {
.open = ux500_pcm_open,
.close = ux500_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = ux500_pcm_hw_params,
.hw_free = ux500_pcm_hw_free,
.prepare = ux500_pcm_prepare,
.trigger = ux500_pcm_trigger,
.pointer = ux500_pcm_pointer,
.mmap = ux500_pcm_mmap
};
int ux500_pcm_new(
struct snd_card *card,
struct snd_soc_dai *dai,
struct snd_pcm *pcm)
{
pr_debug("%s: pcm = %d\n", __func__, (int)pcm);
pcm->info_flags = 0;
strcpy(pcm->name, "UX500_PCM");
pr_debug("%s: pcm->name = %s\n", __func__, pcm->name);
return 0;
}
static void ux500_pcm_free(struct snd_pcm *pcm)
{
pr_debug("%s: Enter\n", __func__);
}
static int ux500_pcm_suspend(struct snd_soc_dai *dai)
{
pr_debug("%s: Enter\n", __func__);
return 0;
}
static int ux500_pcm_resume(struct snd_soc_dai *dai)
{
pr_debug("%s: Enter\n", __func__);
return 0;
}
struct snd_soc_platform ux500_soc_platform = {
.name = "ux500-audio",
.pcm_ops = &ux500_pcm_ops,
.pcm_new = ux500_pcm_new,
.pcm_free = ux500_pcm_free,
.suspend = ux500_pcm_suspend,
.resume = ux500_pcm_resume,
};
EXPORT_SYMBOL(ux500_soc_platform);
static int __init ux500_pcm_init(void)
{
int ret;
pr_debug("%s: Register platform\n", __func__);
ret = snd_soc_register_platform(&ux500_soc_platform);
if (ret < 0)
pr_debug("%s: Error: Failed to register platform\n",
__func__);
return 0;
}
module_init(ux500_pcm_init);
static void __exit ux500_pcm_exit(void)
{
snd_soc_unregister_platform(&ux500_soc_platform);
}
module_exit(ux500_pcm_exit);
MODULE_LICENSE("GPL");