blob: edd22bec2157eb614db4fa96b53350de9e5262e9 [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 "u8500_pcm.h"
#include "u8500_msp_dai.h"
// Local variables -------------------------------------------------------------------
// struct dma_chan
struct u8500_pcm_transfer {
spinlock_t lock;
dma_addr_t addr;
u32 dma_chan_id;
u32 period;
struct dma_chan *dma_chan;
};
static struct snd_pcm_hardware u8500_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 = U8500_PLATFORM_MIN_RATE_PLAYBACK,
.rate_max = U8500_PLATFORM_MAX_RATE_PLAYBACK,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = NMDK_BUFFER_SIZE,
.period_bytes_min = 128,
.period_bytes_max = PAGE_SIZE,
.periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
.periods_max = NMDK_BUFFER_SIZE / 128
};
static struct snd_pcm_hardware u8500_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 = U8500_PLATFORM_MIN_RATE_CAPTURE,
.rate_max = U8500_PLATFORM_MAX_RATE_CAPTURE,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = NMDK_BUFFER_SIZE,
.period_bytes_min = 128,
.period_bytes_max = PAGE_SIZE,
.periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
.periods_max = NMDK_BUFFER_SIZE / 128
};
static struct u8500_pcm_private_t u8500_pcm_private;
// DMA operations -----------------------------------------------------------------------------
/**
* nomadik_alsa_dma_start - used to transmit or recive a dma chunk
* @stream - specifies the playback/record stream structure
*/
static void u8500_pcm_dma_start(struct snd_pcm_substream *substream)
{
unsigned int offset, dma_size;
struct snd_pcm_runtime *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
printk(KERN_DEBUG "u8500_pcm_dma_start: Enter.\n");
dma_size = frames_to_bytes(runtime, runtime->period_size);
offset = dma_size * stream_p->period;
stream_p->old_offset = offset;
printk(KERN_DEBUG "u8500_pcm_dma_start: address = %x size=%d\n", (runtime->dma_addr + offset), dma_size);
if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
u8500_i2s_send_data((void*)(runtime->dma_addr + offset), dma_size, 0);
}
else {
u8500_i2s_receive_data((void *)(runtime->dma_addr + offset), dma_size, 0);
}
stream_p->period++;
stream_p->period %= runtime->periods;
stream_p->periods++;
}
static void u8500_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);
}
/**
* dma_eot_handler
* @data - pointer to structure set in the dma callback handler
* @event - specifies the DMA event: transfer complete/error
*
* This is the PCM tasklet handler linked to a pipe, its role is to tell
* the PCM middler layer whene the buffer position goes across the prescribed
* period size.To inform of this the snd_pcm_period_elapsed is called.
*
* this callback will be called in case of DMA_EVENT_TC only
*/
irqreturn_t u8500_pcm_dma_eot_handler(void *data, int irq)
{
struct snd_pcm_substream *substream = data;
int stream_id = substream->pstr->stream;
u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
/* snd_pcm_period_elapsed() is _not_ to be protected
*/
printk(KERN_DEBUG "u8500_pcm_dma_eot_handler: Enter.\n");
if (substream)
snd_pcm_period_elapsed(substream);
if(stream_p->state == ALSA_STATE_PAUSED)
return IRQ_HANDLED;
if (stream_p->active == 1) {
u8500_pcm_dma_start(substream);
}
return IRQ_HANDLED;
}
EXPORT_SYMBOL(u8500_pcm_dma_eot_handler);
// snd_pcm_ops -----------------------------------------------------------------------------
static int u8500_pcm_open(struct snd_pcm_substream *substream)
{
int status;
struct snd_pcm_runtime *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
printk(KERN_DEBUG "u8500_pcm_open: Enter.\n");
status = 0;
printk(KERN_DEBUG "u8500_pcm_open: Setting HW-config.\n");
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
{
runtime->hw = u8500_pcm_hw_playback;
}
else
{
runtime->hw = u8500_pcm_hw_capture;
}
init_MUTEX(&(stream_p->alsa_sem));
init_completion(&(stream_p->alsa_com));
stream_p->state = ALSA_STATE_RUNNING;
printk(KERN_ALERT "u8500_pcm_open: Exit.\n");
return 0;
}
static int u8500_pcm_close(struct snd_pcm_substream *substream)
{
printk(KERN_DEBUG "u8500_pcm_close: Enter.\n");
return 0;
}
static int u8500_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;
printk(KERN_DEBUG "u8500_pcm_hw_params: Enter.\n");
size = params_buffer_bytes(hw_params);
if (buf) {
if (buf->bytes >= size)
goto out;
u8500_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 u8500_pcm_hw_free(struct snd_pcm_substream *substream)
{
printk(KERN_ALERT "u8500_pcm_hw_free: Enter.\n");
u8500_pcm_dma_hw_free(NULL, substream);
return 0;
}
static int u8500_pcm_prepare(struct snd_pcm_substream *substream)
{
printk(KERN_DEBUG "u8500_pcm_prepare: Enter.\n");
return 0;
}
static int u8500_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
int stream_id = substream->pstr->stream;
u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
printk(KERN_DEBUG "u8500_pcm_trigger: Enter.\n");
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
// Start DMA transfer
printk(KERN_DEBUG "u8500_pcm_trigger: TRIGGER START\n");
if (stream_p->active == 0) {
stream_p->active = 1;
u8500_pcm_dma_start(substream);
break;
}
printk(KERN_DEBUG "u8500_pcm_trigger: Error: H/w is busy\n");
return -EINVAL;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
// Pause DMA transfer
printk(KERN_ALERT "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_PAUSE\n");
if (stream_p->state == ALSA_STATE_RUNNING) {
stream_p->state = ALSA_STATE_PAUSED;
}
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
// Resume DMA transfer
printk(KERN_DEBUG "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_RESUME\n");
if (stream_p->state == ALSA_STATE_PAUSED) {
stream_p->state = ALSA_STATE_RUNNING;
u8500_pcm_dma_start(substream);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
// Stop DMA transfer
printk(KERN_DEBUG "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_STOP\n");
if (stream_p->active == 1) {
stream_p->active = 0;
stream_p->period = 0;
}
break;
default:
printk(KERN_ERR "u8500_pcm_trigger: Invalid command in pcm trigger.\n");
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t u8500_pcm_pointer(struct snd_pcm_substream *substream)
{
unsigned int offset;
struct snd_pcm_runtime *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
printk(KERN_DEBUG "u8500_pcm_pointer: Enter.\n");
offset = bytes_to_frames(runtime, stream_p->old_offset);
if (offset < 0 || stream_p->old_offset < 0)
printk(KERN_DEBUG "u8500_pcm_pointer: Offset=%i %i\n", offset, stream_p->old_offset);
return offset;
}
static int u8500_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
printk(KERN_DEBUG "u8500_pcm_mmap: Enter.\n");
return dma_mmap_coherent(NULL, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
}
static struct snd_pcm_ops u8500_pcm_ops = {
.open = u8500_pcm_open,
.close = u8500_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = u8500_pcm_hw_params,
.hw_free = u8500_pcm_hw_free,
.prepare = u8500_pcm_prepare,
.trigger = u8500_pcm_trigger,
.pointer = u8500_pcm_pointer,
.mmap = u8500_pcm_mmap
};
// snd_soc_platform -----------------------------------------------------------------------------
int u8500_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm)
{
int i;
printk(KERN_DEBUG "u8500_pcm_new: Enter.\n");
printk(KERN_DEBUG "u8500_pcm_new: pcm = %d\n", (int)pcm);
pcm->info_flags = 0;
strcpy(pcm->name, "nomadik_asoc");
for (i = 0; i < NO_OF_STREAMS; i++)
{
;
u8500_pcm_private.streams[i].active = 0;
u8500_pcm_private.streams[i].period = 0;
u8500_pcm_private.streams[i].periods = 0;
u8500_pcm_private.streams[i].old_offset = 0;
}
printk(KERN_DEBUG "u8500_pcm_new: pcm->name = %s\n", pcm->name);
return 0;
}
static void u8500_pcm_free(struct snd_pcm *pcm)
{
printk(KERN_DEBUG "u8500_pcm_free: Enter.\n");
//u8500_pcm_dma_hw_free(NULL, substream);
}
static int u8500_pcm_suspend(struct snd_soc_dai *dai)
{
printk(KERN_DEBUG "u8500_pcm_suspend: Enter.\n");
return 0;
}
static int u8500_pcm_resume(struct snd_soc_dai *dai)
{
printk(KERN_DEBUG "u8500_pcm_resume: Enter.\n");
return 0;
}
struct snd_soc_platform u8500_soc_platform = {
.name = "u8500-audio",
.pcm_ops = &u8500_pcm_ops,
.pcm_new = u8500_pcm_new,
.pcm_free = u8500_pcm_free,
.suspend = u8500_pcm_suspend,
.resume = u8500_pcm_resume,
};
EXPORT_SYMBOL(u8500_soc_platform);