This still does not support bidirectional audio but works when used playback or capture separatelly.
SDMA API used in i.mx31 dma driver has been moved to DMA API.
Please, have patience with me, this is a very experimental version and I am new to this world (audio codecs and linux in general).
Signed-off-by: Javier Martin javier.martin@vista-silicon.com
/* * linux/sound/arm/mx27-pcm.c -- ALSA SoC interface for the Freescale i.MX27 CPU * * Copyright 2009 Vista Silicon S.L. * Author: Javier Martin * javier.martin@vista-silicon.com * * Based on mxc-pcm.c by Liam Girdwood, (C) 2006 Wolfson Microelectronics PLC. * and on mxc-alsa-mc13783 (C) 2006 Freescale. * * 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. * * Revision history * 14th April 2009 Initial version. * */
#include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dma-mapping.h> #include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <asm/dma.h> #include <asm/hardware.h>
#include "imx27-pcm.h" #include "imx27-ssi.h" //For debugging
/* debug */ #define IMX_DEBUG 0 #if IMX_DEBUG #define dbg(format, arg...) printk(format, ## arg) #else #define dbg(format, arg...) #endif
/* Taken from drivers/mxc/ssi/registers.h just provisionally */ #define MXC_SSI1STX0 0x00 #define MXC_SSI1SRX0 0x08 #define MXC_SSI2STX0 0x00 #define MXC_SSI2SRX0 0x08
static const struct snd_pcm_hardware mx27_pcm_hardware = { .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, .buffer_bytes_max = 32 * 1024, .period_bytes_min = 64, .period_bytes_max = 8 * 1024, .periods_min = 2, .periods_max = 255, .fifo_size = 0, };
struct mx27_runtime_data { int dma_ch_tx; //DMA channel number for tx int dma_ch_rx; //DMA channel number for rx int active; //Is the stream active? unsigned int period; //Period number unsigned int periods; //¿? int tx_spin; //¿? spinlock_t dma_lock; //DMA spinlock struct mx27_pcm_dma_param *dma_params; //FIXME this is SDMA api };
/*! * This function stops the current dma transfer for playback * and clears the dma pointers. * * @param substream pointer to the structure of the current stream. * */ static int audio_stop_dma(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data; unsigned int dma_size = frames_to_bytes(runtime, runtime->period_size); unsigned int offset = dma_size * prtd->periods; unsigned long flags;
spin_lock_irqsave(&prtd->dma_lock, flags);
// dbg("MX27 : audio_stop_dma active = 0\n"); prtd->active = 0; prtd->period = 0; prtd->periods = 0;
/* this stops the dma channel and clears the buffer ptrs */
if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if(mxc_dma_disable(prtd->dma_ch_tx)<0) printk("audio_stop_dma: error when stopping dma transfer in channel %d \n", prtd->dma_ch_tx); } else { if(mxc_dma_disable(prtd->dma_ch_rx)<0) printk("audio_stop_dma: error when stopping dma transfer in channel %d \n", prtd->dma_ch_rx); } spin_unlock_irqrestore(&prtd->dma_lock, flags);
return 0; }
/*! * This function is called whenever a new audio block needs to be * transferred to wm8974. The function receives the address and the size * of the new block and start a new DMA transfer. * * @param substream pointer to the structure of the current stream. * */ static int dma_new_period(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data; unsigned int dma_size; unsigned int offset; int ret=0; mxc_dma_requestbuf_t dma_request;
if (prtd->active){ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));; dma_size = frames_to_bytes(runtime, runtime->period_size);
printk("dma_new_period: prtd->period (%x) runtime->periods (%d)\n", prtd->period,runtime->periods); printk("dma_new_period: runtime->period_size (%d) dma_size (%d)\n", (unsigned int)runtime->period_size, runtime->dma_bytes);
offset = dma_size * prtd->period; snd_assert(dma_size <= mx27_pcm_hardware.period_bytes_max);
if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { printk("\ndma_new_period (playback): runtime->dma_area = %x\noffset = %x\ndma_size = %x\n", runtime->dma_area, offset, dma_size); printk("\ndma_new_period : dma_addr is %x\n", (runtime->dma_addr + offset)); dma_request.src_addr = (dma_addr_t)(runtime->dma_addr + offset); #ifdef CONFIG_SND_SOC_MX27_SSI1
dma_request.dst_addr = (dma_addr_t) (SSI1_BASE_ADDR + MXC_SSI1STX0); printk("dst_addr is %x\n", dma_request.dst_addr); printk("src_addr is %x\n", dma_request.src_addr); #else dma_request.dst_addr = (dma_addr_t) (SSI2_BASE_ADDR + MXC_SSI2STX0); #endif } else { printk("\ndma_new_period (capture): runtime->dma_area = %x\noffset = %x\ndma_size = %x\n", runtime->dma_area, offset, dma_size); printk("\ndma_new_period: dma_addr is %x\n", (runtime->dma_addr + offset)); dma_request.dst_addr = (dma_addr_t) (runtime->dma_addr + offset); #ifdef CONFIG_SND_SOC_MX27_SSI1 dma_request.src_addr = (dma_addr_t) (SSI1_BASE_ADDR + MXC_SSI1SRX0); #else dma_request.src_addr = (dma_addr_t) (SSI2_BASE_ADDR + MXC_SSI2SRX0); #endif } dma_request.num_of_bytes = dma_size; //8 printk("dma_new_period: Start DMA offset (%d) size (%d)\n", offset, runtime->dma_bytes); if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { printk("dma_neq_period: configuring tx dma\n"); ret = mxc_dma_config(prtd->dma_ch_tx, &dma_request, 1, MXC_DMA_MODE_WRITE); if(ret<0) { printk("dma_new_period: buffer could not be added for playback transfer\n"); return ret; }
printk("dma_new_period: enable tx dma transfer\n"); ret = mxc_dma_enable(prtd->dma_ch_tx); if(ret<0) { printk("dma_new_period: cannot queue DMA buffer\ (%i)\n", ret); return ret; } } else { ret = mxc_dma_config(prtd->dma_ch_rx, &dma_request, 1, MXC_DMA_MODE_READ); if(ret<0) { printk("dma_new_period: buffer could not be added for capture transfer\n"); return ret; }
printk("dma_new_period: enable dma transfer\n"); ret = mxc_dma_enable(prtd->dma_ch_rx); if(ret<0) { printk("dma_new_period: cannot queue DMA buffer\ (%i)\n", ret); return ret; } }
printk("dma_new_period: transfer enabled\n src_addr = %x\n dst_addr = %x\n num_of_byes = %d\n", (void *) dma_request.src_addr, (void *) dma_request.dst_addr, dma_request.num_of_bytes); prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ prtd->period++; prtd->period %= runtime->periods; } return ret; }
/*! * This is a callback which will be called * when a TX transfer finishes. The call occurs * in interrupt context. * * @param dat pointer to the structure of the current stream. * */ static void audio_dma_irq(void *data, int error, unsigned int count) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; struct mx27_runtime_data *prtd; unsigned int dma_size; unsigned int previous_period; unsigned int offset;
substream = data; runtime = substream->runtime; prtd = runtime->private_data; previous_period = prtd->periods; dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * previous_period;
prtd->tx_spin = 0; prtd->periods++; prtd->periods %= runtime->periods;
// printk("audio_dma_irq: irq per %d offset %x\n", prtd->periods, offset);
/* * Give back to the CPU the access to the non cached memory */ // if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) // dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, // DMA_TO_DEVICE); // else // dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, // DMA_FROM_DEVICE); /* * If we are getting a callback for an active stream then we inform * the PCM middle layer we've finished a period */ if (prtd->active) snd_pcm_period_elapsed(substream);
/* * Trig next DMA transfer */ dma_new_period(substream); }
/*! * This function configures the hardware to allow audio * playback operations. It is called by ALSA framework. * * @param substream pointer to the structure of the current stream. * * @return 0 on success, -1 otherwise. */ static int snd_mxc_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data;
prtd->period = 0; prtd->periods = 0;
return 0; }
static int mxc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data; int ret;
if((ret=snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) { printk("imx27-pcm: failed to malloc pcm pages\n"); return ret; } printk("mxc_pcm_hw_params: snd_pcm_lib_malloc pages has return OK\n"); printk("IMX27: snd_imx27_audio_hw_params runtime->dma_addr 0x(%x)\n", (unsigned int)runtime->dma_addr); printk("IMX27: snd_imx27_audio_hw_params runtime->dma_area 0x(%x)\n", (unsigned int)runtime->dma_area); printk("IMX27: snd_imx27_audio_hw_params runtime->dma_bytes 0x(%x)\n", (unsigned int)runtime->dma_bytes);
return ret; }
static int mxc_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data;
mxc_dma_free(prtd->dma_ch_tx); mxc_dma_free(prtd->dma_ch_rx); snd_pcm_lib_free_pages(substream);
return 0; }
static int mxc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct mx27_runtime_data *prtd = substream->runtime->private_data; int ret = 0;
switch (cmd) { case SNDRV_PCM_TRIGGER_START: prtd->tx_spin = 0; /* requested stream startup */ prtd->active = 1; printk("mxc_pcm_trigger: starting dma_new_period\n"); ret = dma_new_period(substream); break; case SNDRV_PCM_TRIGGER_STOP: /* requested stream shutdown */ printk("mxc_pcm_trigger: stopping dma transfer\n"); ret = audio_stop_dma(substream); break; default: ret = -EINVAL; break; }
return ret; }
static snd_pcm_uframes_t mxc_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd = runtime->private_data; unsigned int offset = 0;
printk("mxc_pcm_pointer:\n runtime->period_size=%d\n prtd->periods=%d\n runtime->buffer_size=%d\n prtd->tx_spin=%d\n", runtime->period_size, prtd->periods, runtime->buffer_size, prtd->tx_spin); /* tx_spin value is used here to check if a transfer is active */ if (prtd->tx_spin){ offset = (runtime->period_size * (prtd->periods)) + (runtime->period_size >> 1); if (offset >= runtime->buffer_size) offset = runtime->period_size >> 1; } else { offset = (runtime->period_size * (prtd->periods)); if (offset >= runtime->buffer_size) offset = 0; } printk("mxc_pcm_pointer: pointer offset %x\n", offset);
return offset; }
//Alocation of substream->runtime->private_data static int mxc_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mx27_runtime_data *prtd; int ret;
snd_soc_set_runtime_hwparams(substream, &mx27_pcm_hardware);
//?? if ((ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) return ret;
if((prtd = kzalloc(sizeof(struct mx27_runtime_data), GFP_KERNEL)) == NULL) { ret = -ENOMEM; goto out; }
runtime->private_data = prtd;
if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
#ifdef CONFIG_SND_SOC_MX27_SSI1 printk("mxc_pcm_open: Requesting MXC_DMA_SSI1_16BIT_TX0 dma channel\n"); prtd->dma_ch_tx = mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); //MXC_DMA_TEST_RAM2RAM #else prtd->dma_ch_tx = mxc_dma_request(MXC_DMA_SSI2_16BIT_TX0, "ALSA TX DMA"); #endif if(prtd->dma_ch_tx < 0) { printk("error requesting a write(dma to device) dma channel\n"); return ret; }
//Set dma callback printk("mxc_pcm_open: Setting tx dma callback function\n"); ret = mxc_dma_callback_set(prtd->dma_ch_tx, (mxc_dma_callback_t) audio_dma_irq, (void *)substream); if(ret<0) { printk("error setting dma callback function\n"); return ret; }
return 0; } else { #ifdef CONFIG_SND_SOC_MX27_SSI1 printk("mxc_pcm_open: Requesting MXC_DMA_SSI1_16BIT_RX0 dma channel\n"); prtd->dma_ch_rx = mxc_dma_request(MXC_DMA_SSI1_16BIT_RX0, "ALSA RX DMA"); #else prtd->dma_ch_rx = mxc_dma_request(MXC_DMA_SSI2_16BIT_RX0, "ALSA RX DMA"); #endif if(prtd->dma_ch_rx < 0) { printk("error requesting a read(dma from device) dma channel\n"); return ret; }
//Set dma callback printk("mxc_pcm_open: Setting rx dma callback function\n"); if(mxc_dma_callback_set(prtd->dma_ch_rx, (mxc_dma_callback_t) audio_dma_irq, (void *)substream)<0) printk("error setting dma callback function\n");
return 0; }
out: return ret; }
static int mxc_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct mxc_runtime_data *prtd = runtime->private_data;
kfree(prtd);
return 0; }
static int mxc_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; return dma_mmap_writecombine(substream->pcm->card->dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); }
struct snd_pcm_ops mxc_pcm_ops = { .open = mxc_pcm_open, .close = mxc_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = mxc_pcm_hw_params, .hw_free = mxc_pcm_hw_free, .prepare = snd_mxc_prepare, .trigger = mxc_pcm_trigger, .pointer = mxc_pcm_pointer, .mmap = mxc_pcm_mmap, };
static u64 mxc_pcm_dmamask = 0xffffffff;
static int imx31_pcm_preallocate_dma_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 = mx27_pcm_hardware.buffer_bytes_max; buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL;
//Reserve uncached-buffered memory area for DMA buf->area = dma_alloc_writecombine(pcm->card->dev, size, &buf->addr, GFP_KERNEL);
printk("imx27_pcm: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", (void *) buf->area, (void *) buf->addr, size);
if (!buf->area) return -ENOMEM;
buf->bytes = size; return 0; }
static void imx31_pcm_free_dma_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 (!substream) continue;
buf = &substream->dma_buffer; if (!buf->area) continue;
dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, buf->addr); buf->area = NULL; } }
int mxc_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 = &mxc_pcm_dmamask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff;
if (dai->playback.channels_min) { ret = imx31_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); printk("mxc_pcm_new: preallocate playback buffer\n"); if (ret) goto out; }
if (dai->capture.channels_min) { ret = imx31_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); printk("mxc_pcm_new: preallocate capture buffer\n"); if (ret) goto out; } out: return ret; }
struct snd_soc_platform imx27_soc_platform = { .name = "mxc-audio", .pcm_ops = &mxc_pcm_ops, .pcm_new = mxc_pcm_new, .pcm_free = imx31_pcm_free_dma_buffers, };
EXPORT_SYMBOL_GPL(imx27_soc_platform);
MODULE_AUTHOR("Javier Martin"); MODULE_DESCRIPTION("Freescale i.MX27 PCM DMA module"); MODULE_LICENSE("GPL");
/* * mx27-pcm.h :- ASoC platform header for Freescale i.MX27 * * 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. */
#ifndef _MXC_PCM_H #define _MXC_PCM_H
#include <asm/arch/dma.h>
/* AUDMUX regs definition - MOVE to asm/arch when stable #define AUDMUX_IO_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR)
#define DAM_PTCR1 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x00))) #define DAM_PDCR1 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x04))) #define DAM_PTCR2 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x08))) #define DAM_PDCR2 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x0C))) #define DAM_PTCR3 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x10))) #define DAM_PDCR3 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x14))) #define DAM_PTCR4 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x18))) #define DAM_PDCR4 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x1C))) #define DAM_PTCR5 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x20))) #define DAM_PDCR5 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x24))) #define DAM_PTCR6 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x28))) #define DAM_PDCR6 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x2C))) #define DAM_PTCR7 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x30))) #define DAM_PDCR7 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x34))) #define DAM_CNMCR (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x38))) #define DAM_PTCR(a) (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + a*8))) #define DAM_PDCR(a) (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 4 + a*8)))
#define AUDMUX_PTCR_TFSDIR (1 << 31) #define AUDMUX_PTCR_TFSSEL(x, y) ((x << 30) | (((y - 1) & 0x7) << 27)) #define AUDMUX_PTCR_TCLKDIR (1 << 26) #define AUDMUX_PTCR_TCSEL(x, y) ((x << 25) | (((y - 1) & 0x7) << 22)) #define AUDMUX_PTCR_RFSDIR (1 << 21) #define AUDMUX_PTCR_RFSSEL(x, y) ((x << 20) | (((y - 1) & 0x7) << 17)) #define AUDMUX_PTCR_RCLKDIR (1 << 16) #define AUDMUX_PTCR_RCSEL(x, y) ((x << 15) | (((y - 1) & 0x7) << 12)) #define AUDMUX_PTCR_SYN (1 << 11)
#define AUDMUX_FROM_TXFS 0 #define AUDMUX_FROM_RXFS 1
#define AUDMUX_PDCR_RXDSEL(x) (((x - 1) & 0x7) << 13) #define AUDMUX_PDCR_TXDXEN (1 << 12) #define AUDMUX_PDCR_MODE(x) (((x) & 0x3) << 8) #define AUDMUX_PDCR_INNMASK(x) (((x) & 0xff) << 0)
#define AUDMUX_CNMCR_CEN (1 << 18) #define AUDMUX_CNMCR_FSPOL (1 << 17) #define AUDMUX_CNMCR_CLKPOL (1 << 16) #define AUDMUX_CNMCR_CNTHI(x) (((x) & 0xff) << 8) #define AUDMUX_CNMCR_CNTLOW(x) (((x) & 0xff) << 0) */
/* Previous defines are only valid for imx31 */ #define AUDMUX_IO_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR)
#define DAM_HPCR1 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x00))) #define DAM_HPCR2 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x04))) #define DAM_HPCR3 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x08))) #define DAM_PPCR1 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x10))) #define DAM_PPCR2 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x14))) #define DAM_PPCR3 (*((volatile u32 *)(AUDMUX_IO_BASE_ADDR + 0x1C)))
#define AUDMUX_HPCR_TFSDIR (1 << 31) #define AUDMUX_HPCR_TCLKDIR (1 << 30) #define AUDMUX_HPCR_TFCSEL(x) (((x) & 0xff) << 26) #define AUDMUX_HPCR_RXDSEL(x) (((x) & 0x7) << 13) #define AUDMUX_HPCR_SYN (1 << 12)
#define AUDMUX_PPCR_TFSDIR (1 << 31) #define AUDMUX_PPCR_TCLKDIR (1 << 30) #define AUDMUX_PPCR_TFCSEL(x) (((x) & 0xff) << 26) #define AUDMUX_PPCR_RXDSEL(x) (((x) & 0x7) << 13) #define AUDMUX_PPCR_SYN (1 << 12)
struct mx27_pcm_dma_params { char *name; /* stream identifier */ mxc_dma_requestbuf_t buf_params; /*DMA buffer params */
};
extern struct snd_soc_dai mxc_ssi_dai[3];
/* platform data */ extern struct snd_soc_platform imx27_soc_platform;
#endif