/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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");