summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/dai/sprd-dmaengine-pcm.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /sound/soc/sprd/dai/sprd-dmaengine-pcm.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'sound/soc/sprd/dai/sprd-dmaengine-pcm.c')
-rw-r--r--sound/soc/sprd/dai/sprd-dmaengine-pcm.c1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/sprd-dmaengine-pcm.c b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c
new file mode 100644
index 00000000..548601f8
--- /dev/null
+++ b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c
@@ -0,0 +1,1069 @@
+/*
+ * sound/soc/sprd/dai/sprd-dmaengine-pcm.c
+ *
+ * SpreadTrum DMA for the pcm stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" PCM ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/of.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <soc/sprd/sprd-audio.h>
+#include "sprd-asoc-common.h"
+#include "sprd-dmaengine-pcm.h"
+#include "vaudio.h"
+#include <soc/sprd/i2s.h>
+#include "dfm.h"
+
+#ifndef DMA_LINKLIST_CFG_NODE_SIZE
+#define DMA_LINKLIST_CFG_NODE_SIZE (sizeof(struct sprd_dma_cfg))
+#endif
+
+struct sprd_dma_callback_data {
+ struct snd_pcm_substream *substream;
+ struct dma_chan *dma_chn;
+};
+
+struct sprd_runtime_data {
+ int dma_addr_offset;
+ struct sprd_pcm_dma_params *params;
+ struct dma_chan *dma_chn[2];
+ struct sprd_dma_cfg *dma_cfg_array;
+ struct sprd_dma_callback_data *dma_pdata;
+ dma_addr_t dma_cfg_phy[2];
+ void *dma_cfg_virt[2];
+ struct dma_async_tx_descriptor *dma_tx_des[2];
+ dma_cookie_t cookie[2];
+ int int_pos_update[2];
+ int burst_len;
+ int hw_chan;
+ int dma_pos_pre[2];
+ int interleaved;
+ int cb_called;
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ int buffer_in_iram;
+#endif
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+ struct snd_info_entry *proc_info_entry;
+#endif
+};
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#define SPRD_AUDIO_DMA_NODE_SIZE (1024)
+#endif
+
+static const struct snd_pcm_hardware sprd_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = VBC_FIFO_FRAME_NUM * 4,
+ /* non limit */
+ .period_bytes_max = VBC_FIFO_FRAME_NUM * 4 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = VBC_BUFFER_BYTES_MAX,
+};
+
+static const struct snd_pcm_hardware sprd_i2s_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = 8 * 2,
+ /* non limit */
+ .period_bytes_max = 32 * 2 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = I2S_BUFFER_BYTES_MAX,
+};
+
+atomic_t lightsleep_refcnt;
+
+int sprd_lightsleep_disable(const char *id, int disalbe)
+ __attribute__ ((weak, alias("__sprd_lightsleep_disable")));
+
+static int __sprd_lightsleep_disable(const char *id, int disable)
+{
+ sp_asoc_pr_dbg("NO lightsleep control function %d\n", disable);
+ return 0;
+}
+
+static inline int sprd_is_vaudio(struct snd_soc_dai *cpu_dai)
+{
+ return ((cpu_dai->driver->id == VAUDIO_MAGIC_ID)
+ || (cpu_dai->driver->id == VAUDIO_MAGIC_ID + 1));
+}
+
+static inline int sprd_is_i2s(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == I2S_MAGIC_ID);
+}
+
+static inline int sprd_is_dfm(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == DFM_MAGIC_ID);
+}
+
+static inline const char *sprd_dai_pcm_name(struct snd_soc_dai *cpu_dai)
+{
+ if (sprd_is_i2s(cpu_dai)) {
+ return "I2S";
+ } else if (sprd_is_vaudio(cpu_dai)) {
+ return "VAUDIO";
+ } else if (sprd_is_dfm(cpu_dai)) {
+ return "DFM";
+ }
+ return "VBC";
+}
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+static char *s_mem_for_iram = 0;
+#endif
+static char *s_iram_remap_base = 0;
+
+static int sprd_buffer_iram_backup(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+#endif
+ if (!s_iram_remap_base) {
+ s_iram_remap_base =
+ ioremap_nocache(SPRD_IRAM_ALL_PHYS, SPRD_IRAM_ALL_SIZE);
+ }
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ if (!s_mem_for_iram) {
+ s_mem_for_iram = kzalloc(SPRD_IRAM_ALL_SIZE, GFP_KERNEL);
+ } else {
+ sp_asoc_pr_dbg("IRAM is Backup, Be Careful use IRAM!\n");
+ return 0;
+ }
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM Backup Error!\n");
+ return -ENOMEM;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_fromio(s_mem_for_iram, iram_start, SPRD_IRAM_ALL_SIZE);
+#endif
+ return 0;
+}
+
+static int sprd_buffer_iram_restore(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM not Backup\n");
+ return 0;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_toio(iram_start, s_mem_for_iram, SPRD_IRAM_ALL_SIZE);
+ kfree(s_mem_for_iram);
+ s_mem_for_iram = 0;
+#endif
+ return 0;
+}
+#endif
+
+static inline int sprd_pcm_is_interleaved(struct snd_pcm_runtime *runtime)
+{
+ return (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+}
+
+#define PCM_DIR_NAME(stream) (stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Captrue")
+
+static int sprd_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_runtime_data *rtd;
+ struct i2s_config *config;
+ int burst_len;
+ int hw_chan;
+ int ret;
+
+ sp_asoc_pr_info("%s Open %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ snd_soc_set_runtime_hwparams(substream, &sprd_i2s_pcm_hardware);
+ config = srtd->cpu_dai->ac97_pdata;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ burst_len = I2S_FIFO_DEPTH - config->tx_watermark;
+ } else {
+ burst_len = config->rx_watermark;
+ }
+ burst_len <<= config->byte_per_chan;
+ hw_chan = 1;
+ } else {
+ snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware);
+ burst_len = (VBC_FIFO_FRAME_NUM * 4);
+ hw_chan = 2;
+ }
+
+ /*
+ * For mysterious reasons (and despite what the manual says)
+ * playback samples are lost if the DMA count is not a multiple
+ * of the DMA burst size. Let's add a rule to enforce that.
+ */
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ ret = -ENOMEM;
+ rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
+ if (!rtd)
+ goto out;
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(srtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ rtd->dma_cfg_virt[0] =
+ (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ &rtd->dma_cfg_phy[0], GFP_KERNEL);
+ if (!rtd->dma_cfg_virt[0])
+ goto err2;
+ rtd->dma_cfg_virt[1] =
+ (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ &rtd->dma_cfg_phy[1], GFP_KERNEL);
+ if (!rtd->dma_cfg_virt[1])
+ goto err3;
+ if (atomic_inc_return(&lightsleep_refcnt) == 1)
+ sprd_lightsleep_disable("audio", 1);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ runtime->hw.periods_max =
+ SPRD_AUDIO_DMA_NODE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE;
+ runtime->hw.buffer_bytes_max =
+ SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ rtd->buffer_in_iram = 1;
+ rtd->dma_cfg_virt[0] = s_iram_remap_base + runtime->hw.buffer_bytes_max;
+ rtd->dma_cfg_phy[0] = SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max;
+ rtd->dma_cfg_virt[1] = ((unsigned long)(rtd->dma_cfg_virt[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg))) + 15) & (~0xf);
+ rtd->dma_cfg_phy[1] = ((unsigned int)(rtd->dma_cfg_phy[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg)))+ 15) & (~0xf);
+ }
+#endif
+ rtd->dma_cfg_array =
+ (struct sprd_dma_cfg *)kzalloc(hw_chan * runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),GFP_KERNEL);
+ if (!rtd->dma_cfg_array)
+ goto err4;
+
+ rtd->dma_pdata =
+ (struct sprd_dma_callback_data *)kzalloc(hw_chan * sizeof(struct sprd_dma_callback_data),GFP_KERNEL);
+ if (!rtd->dma_pdata)
+ goto err5;
+
+ rtd->dma_chn[0] = rtd->dma_chn[1] = NULL;
+ rtd->dma_tx_des[0] = rtd->dma_tx_des[1] = NULL;
+ rtd->cookie[0] = rtd->cookie[1] = 0;
+
+ rtd->burst_len = burst_len;
+ rtd->hw_chan = hw_chan;
+
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+
+err5:
+ pr_err("ERR:dma_pdata alloc failed!\n");
+ kfree(rtd->dma_cfg_array);
+err4:
+ pr_err("ERR:dma_cfg_array alloc failed!\n");
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram)
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]);
+err3:
+ pr_err("ERR:dma_cfg_virt[1] alloc failed!\n");
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram)
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]);
+err2:
+ pr_err("ERR:dma_cfg_virt[0] alloc failed!\n");
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+#endif
+ kfree(rtd);
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+out:
+ return ret;
+}
+
+static int sprd_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ sp_asoc_pr_info("%s Close %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ kfree(rtd);
+ return 0;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+ else {
+#endif
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ }
+#endif
+ kfree(rtd->dma_pdata);
+ kfree(rtd->dma_cfg_array);
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram) {
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]);
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ }
+#endif
+ kfree(rtd);
+
+ return 0;
+}
+
+static void sprd_pcm_dma_buf_done(void *data)
+{
+ struct sprd_dma_callback_data *dma_cb_data = (struct sprd_dma_callback_data*)data;
+ struct snd_pcm_runtime *runtime = dma_cb_data->substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ int i = 0;
+
+ if (!rtd->cb_called) {
+ rtd->cb_called = 1;
+ sp_asoc_pr_info("DMA Callback CALL\n");
+ }
+
+ if (rtd->hw_chan == 1)
+ goto irq_fast;
+
+ for (i = 0; i < 2; i++) {
+ if (dma_cb_data->dma_chn == rtd->dma_chn[i]) {
+ rtd->int_pos_update[i] = 1;
+
+ if (rtd->dma_chn[1 - i]) {
+ if (rtd->int_pos_update[1 - i])
+ goto irq_ready;
+ } else {
+ goto irq_ready;
+ }
+ }
+ }
+ goto irq_ret;
+irq_ready:
+ rtd->int_pos_update[0] = 0;
+ rtd->int_pos_update[1] = 0;
+irq_fast:
+ snd_pcm_period_elapsed(dma_cb_data->substream);
+irq_ret:
+ return;
+}
+
+/*
+ * proc interface
+ */
+
+//#ifdef CONFIG_SND_VERBOSE_PROCFS
+#if 0
+static void sprd_pcm_proc_dump_reg(int id, struct snd_info_buffer *buffer)
+{
+ u32 reg, base_reg;
+ u32 offset = sci_dma_dump_reg(id, &base_reg);
+ for (reg = base_reg; reg < base_reg + offset; reg += 0x10) {
+ snd_iprintf(buffer, "0x%04x | 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ (unsigned int)(reg - base_reg),
+ __raw_readl((void __iomem *)(reg + 0x00)),
+ __raw_readl((void __iomem *)(reg + 0x04)),
+ __raw_readl((void __iomem *)(reg + 0x08)),
+ __raw_readl((void __iomem *)(reg + 0x0C))
+ );
+ }
+}
+
+static void sprd_pcm_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_pcm_substream *substream = entry->private_data;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ int i;
+
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ snd_iprintf(buffer, "Channel%d Config\n",
+ rtd->uid_cid_map[i]);
+ sprd_pcm_proc_dump_reg(rtd->uid_cid_map[i], buffer);
+ }
+ }
+}
+
+static void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+ struct snd_info_entry *entry;
+ struct snd_pcm_str *pstr = substream->pstr;
+ struct snd_pcm *pcm = pstr->pcm;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+
+ if ((entry =
+ snd_info_create_card_entry(pcm->card, "DMA",
+ pstr->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, sprd_pcm_proc_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ rtd->proc_info_entry = entry;
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ snd_info_free_entry(rtd->proc_info_entry);
+ rtd->proc_info_entry = NULL;
+}
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+static inline void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+}
+#endif
+
+static int sprd_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_pcm_dma_params *dma_data;
+ struct dma_chan *dma_chn_request;
+ struct sprd_dma_cfg *dma_config_ptr[2];
+ struct sprd_dma_callback_data *dma_pdata_ptr[2];
+ dma_cap_mask_t mask;
+ size_t totsize = params_buffer_bytes(params);
+ size_t period = params_period_bytes(params);
+ dma_addr_t dma_buff_phys[2];
+ struct i2s_config *config = NULL;
+ enum dma_filter_param chn_type;
+ int ret = 0;
+ int i, j = 0;
+ int used_chan_count;
+
+ dma_data = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream);
+ if (!dma_data)
+ goto no_dma;
+
+ used_chan_count = params_channels(params);
+ sp_asoc_pr_info("chan=%d totsize=%d period=%d\n", used_chan_count,
+ totsize, period);
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ config = srtd->cpu_dai->ac97_pdata;
+ used_chan_count = rtd->hw_chan;
+ } else {
+ rtd->interleaved = (used_chan_count == 2)
+ && sprd_pcm_is_interleaved(runtime);
+ if (rtd->interleaved) {
+ sp_asoc_pr_dbg("Interleaved Access\n");
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_data->desc.src_step = 4;
+ } else {
+ dma_data->desc.des_step = 4;
+ }
+ } else {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_data->desc.src_step = 2;
+ } else {
+ dma_data->desc.des_step = 2;
+ }
+ }
+ }
+
+ /* this may get called several times by oss emulation
+ * with different params */
+ if (rtd->params == NULL) {
+ rtd->params = dma_data;
+ for (i = 0; i < used_chan_count; i++) {
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY|DMA_SG,mask);
+ /* request dma channel */
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_USE_AON_DMA
+ chn_type = AP_FULL_DMA;
+#else
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ && (!sprd_is_i2s(srtd->cpu_dai))) {
+ chn_type = AON_FULL_DMA;
+ } else {
+ chn_type = AP_FULL_DMA;
+ }
+#endif
+ dma_chn_request = dma_request_channel(mask, sprd_dma_filter_fn, (void *)&chn_type);
+ if (!dma_chn_request) {
+ pr_err("ERR:PCM Request DMA Error %d\n",
+ dma_data->channels[i]);
+ for (i--; i >= 0; i--) {
+ rtd->dma_chn[i] = NULL;
+ rtd->dma_tx_des[i] = NULL;
+ rtd->cookie[i] = 0;
+ rtd->params = NULL;
+ }
+ goto hw_param_err;
+ }
+ rtd->dma_chn[i] = dma_chn_request;
+ sp_asoc_pr_dbg("Chan%d DMA ID=%d, chn_type:%d \n",
+ rtd->dma_chn[i]->chan_id,
+ rtd->params->channels[i], chn_type);
+ }
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totsize;
+
+ rtd->dma_addr_offset = (totsize / used_chan_count);
+ if (sprd_pcm_is_interleaved(runtime))
+ rtd->dma_addr_offset = 2;
+
+ for (i = 0; i < used_chan_count; i++) {
+ dma_config_ptr[i] = rtd->dma_cfg_array + i * (runtime->hw.periods_max);
+ memset(dma_config_ptr[i],0,sizeof(struct sprd_dma_cfg) * runtime->hw.periods_max);
+ dma_pdata_ptr[i] = rtd->dma_pdata + i;
+ memset(dma_pdata_ptr[i],0,sizeof(struct sprd_dma_callback_data));
+ dma_pdata_ptr[i]->dma_chn = rtd->dma_chn[i];
+ dma_pdata_ptr[i]->substream = substream;
+ dma_buff_phys[i] = runtime->dma_addr + i * rtd->dma_addr_offset;
+ }
+
+ do {
+ for (i = 0; i < used_chan_count; i++) {
+ (dma_config_ptr[i]+j)->datawidth = 1;
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ if (substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK) {
+ (dma_config_ptr[i]+j)->fragmens_len =
+ (I2S_FIFO_DEPTH -
+ config->tx_watermark) *
+ (dma_config_ptr[i]+j)->datawidth;
+ } else {
+ (dma_config_ptr[i]+j)->fragmens_len =
+ config->rx_watermark *
+ (dma_config_ptr[i]+j)->datawidth;
+ }
+ } else {
+ (dma_config_ptr[i]+j)->fragmens_len = dma_data->desc.fragmens_len;
+ }
+ (dma_config_ptr[i]+j)->block_len = period / used_chan_count;
+ (dma_config_ptr[i]+j)->transcation_len = 0;
+ (dma_config_ptr[i]+j)->req_mode = FRAG_REQ_MODE;
+ (dma_config_ptr[i]+j)->irq_mode= BLK_DONE;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step;
+ (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step;
+ (dma_config_ptr[i]+j)->src_addr = dma_buff_phys[i];
+ (dma_config_ptr[i]+j)->des_addr = dma_data->dev_paddr[i];
+ } else {
+ (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step;
+ (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step;
+ (dma_config_ptr[i]+j)->src_addr = dma_data->dev_paddr[i];
+ (dma_config_ptr[i]+j)->des_addr = dma_buff_phys[i];
+ }
+ dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len;
+ if (rtd->interleaved)
+ dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len;
+
+ (dma_config_ptr[i]+j)->link_cfg_v = (unsigned long)(rtd->dma_cfg_virt[i]);
+ (dma_config_ptr[i]+j)->link_cfg_p = (unsigned long)(rtd->dma_cfg_phy[i]);
+ (dma_config_ptr[i]+j)->dev_id = dma_data->channels[i];
+ }
+
+ if (period > totsize)
+ period = totsize;
+ j++;
+ } while (totsize -= period);
+ for (i = 0; i < used_chan_count; i++) {
+ (dma_config_ptr[i]+j-1)->is_end = 2;
+ }
+
+ sp_asoc_pr_dbg("Node Size:%d\n", j);
+
+ /* config dma channel */
+ ret = dmaengine_device_control(rtd->dma_chn[0],
+ DMA_SLAVE_CONFIG,
+ (unsigned long)(dma_config_ptr[0]));
+ if(ret < 0){
+ sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[0]->chan_id);
+ goto hw_param_err;
+ }
+ /* get dma desc from dma config */
+ rtd->dma_tx_des[0] =
+ rtd->dma_chn[0]->device->device_prep_dma_memcpy(rtd->dma_chn[0], 0, 0, 0,
+ DMA_CFG_FLAG|DMA_HARDWARE_FLAG);
+ if(!rtd->dma_tx_des[0]) {
+ sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[0]->chan_id);
+ goto hw_param_err;
+ }
+
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n",
+ __func__,rtd->dma_chn[0]->chan_id);
+ rtd->dma_tx_des[0]->callback = sprd_pcm_dma_buf_done;
+ rtd->dma_tx_des[0]->callback_param = (void *)(dma_pdata_ptr[0]);
+ }
+
+ if (used_chan_count > 1) {
+ /* config dma channel */
+ ret = dmaengine_device_control(rtd->dma_chn[1],
+ DMA_SLAVE_CONFIG,
+ (unsigned long)(dma_config_ptr[1]));
+ if(ret < 0){
+ sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[1]->chan_id);
+ goto hw_param_err;
+ }
+ /* get dma desc from dma config */
+ rtd->dma_tx_des[1] =
+ rtd->dma_chn[1]->device->device_prep_dma_memcpy(rtd->dma_chn[1], 0, 0, 0,
+ DMA_CFG_FLAG|DMA_HARDWARE_FLAG);
+ if(!rtd->dma_tx_des[1]) {
+ sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[1]->chan_id);
+ goto hw_param_err;
+ }
+
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n",
+ __func__,rtd->dma_chn[1]->chan_id);
+ rtd->dma_tx_des[1]->callback = sprd_pcm_dma_buf_done;
+ rtd->dma_tx_des[1]->callback_param = (void *)(dma_pdata_ptr[1]);
+ }
+
+ }
+ sprd_pcm_proc_init(substream);
+
+ goto ok_go_out;
+
+no_dma:
+ sp_asoc_pr_dbg("no dma\n");
+ rtd->params = NULL;
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = totsize;
+ return ret;
+hw_param_err:
+ sp_asoc_pr_dbg("hw_param_err\n");
+ok_go_out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static int sprd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int i;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (dma) {
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_chn[i]) {
+ dma_release_channel(rtd->dma_chn[i]);
+ rtd->dma_chn[i] = NULL;
+ rtd->dma_tx_des[i] = NULL;
+ rtd->cookie[i] = 0;
+ }
+ }
+ rtd->params = NULL;
+ }
+
+ sprd_pcm_proc_done(substream);
+
+ return 0;
+}
+
+static int sprd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int ret = 0;
+ int i;
+ if (!dma) {
+ sp_asoc_pr_dbg("no trigger");
+ return 0;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_tx_des[i]) {
+ rtd->cookie[i] = dmaengine_submit(rtd->dma_tx_des[i]);
+ }
+ }
+ dma_issue_pending_all();
+ sp_asoc_pr_info("S\n");
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_chn[i]) {
+ dmaengine_pause(rtd->dma_chn[i]);
+ }
+ }
+ rtd->cb_called = 0;
+ sp_asoc_pr_info("E\n");
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ snd_pcm_uframes_t x;
+ int now_pointer;
+ int bytes_of_pointer = 0;
+ int shift = 1;
+ int sel_max = 0;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no pointer");
+ return 0;
+ }
+
+ if (rtd->interleaved)
+ shift = 0;
+ if (rtd->dma_chn[0]) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[0],
+ rtd->cookie[0], substream) -
+ runtime->dma_addr;
+ bytes_of_pointer = now_pointer;
+ }
+ if (rtd->dma_chn[1]) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[1],
+ rtd->cookie[1], substream) -
+ runtime->dma_addr - rtd->dma_addr_offset;
+ if (!bytes_of_pointer) {
+ bytes_of_pointer = now_pointer;
+ } else {
+ sel_max = (bytes_of_pointer < rtd->dma_pos_pre[0]);
+ sel_max ^= (now_pointer < rtd->dma_pos_pre[1]);
+ rtd->dma_pos_pre[0] = bytes_of_pointer;
+ rtd->dma_pos_pre[1] = now_pointer;
+ if (sel_max) {
+ bytes_of_pointer =
+ max(bytes_of_pointer, now_pointer) << shift;
+ } else {
+ bytes_of_pointer =
+ min(bytes_of_pointer, now_pointer) << shift;
+ }
+ }
+ }
+ x = bytes_to_frames(runtime, bytes_of_pointer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+
+#if 0
+ sp_asoc_pr_dbg("p=%d f=%d\n", bytes_of_pointer, x);
+#endif
+ return x;
+}
+
+static int sprd_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no mmap");
+ return 0;
+ }
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+#else
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ return remap_pfn_range(vma, vma->vm_start,
+ runtime->dma_addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+#endif
+}
+
+static struct snd_pcm_ops sprd_pcm_ops = {
+ .open = sprd_pcm_open,
+ .close = sprd_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = sprd_pcm_hw_params,
+ .hw_free = sprd_pcm_hw_free,
+ .prepare = sprd_pcm_prepare,
+ .trigger = sprd_pcm_trigger,
+ .pointer = sprd_pcm_pointer,
+ .mmap = sprd_pcm_mmap,
+};
+
+static int sprd_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = AUDIO_BUFFER_BYTES_MAX;
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ if (sprd_is_i2s(rtd->cpu_dai)) {
+ size = I2S_BUFFER_BYTES_MAX;
+ } else {
+ size = VBC_BUFFER_BYTES_MAX;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(rtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ buf->private_data = buf;
+ buf->area = (void *)(s_iram_remap_base);
+ buf->addr = SPRD_IRAM_ALL_PHYS;
+ size = SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ }
+#endif
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static u64 sprd_pcm_dmamask = DMA_BIT_MASK(32);
+static struct snd_dma_buffer *save_p_buf = 0;
+#define VBC_CHAN (2)
+static struct snd_dma_buffer *save_c_buf[VBC_CHAN] = { 0 };
+
+static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_pcm_substream *substream;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s %s\n", __func__, sprd_dai_pcm_name(cpu_dai));
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &sprd_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (substream) {
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_i2s(cpu_dai) || !save_p_buf) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_p_buf = buf;
+ }
+ sp_asoc_pr_dbg("Playback alloc memery\n");
+ } else {
+ memcpy(buf, save_p_buf, sizeof(*buf));
+ sp_asoc_pr_dbg("Playback share memery\n");
+ }
+ }
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ if (substream) {
+ int id = cpu_dai->driver->id;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_vaudio(cpu_dai)) {
+ id -= VAUDIO_MAGIC_ID;
+ }
+ if (sprd_is_i2s(cpu_dai) || !save_c_buf[id]) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_c_buf[id] = buf;
+ }
+ sp_asoc_pr_dbg("Capture alloc memery %d\n", id);
+ } else {
+ memcpy(buf, save_c_buf[id], sizeof(*buf));
+ sp_asoc_pr_dbg("Capture share memery %d\n", id);
+ }
+ }
+out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static void sprd_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (buf->private_data)
+ sprd_buffer_iram_restore();
+ else
+#endif
+ dma_alloc_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ if (buf == save_p_buf) {
+ save_p_buf = 0;
+ }
+ for (i = 0; i < VBC_CHAN; i++) {
+ if (buf == save_c_buf[i]) {
+ save_c_buf[i] = 0;
+ }
+ }
+ }
+}
+
+static struct snd_soc_platform_driver sprd_soc_platform = {
+ .ops = &sprd_pcm_ops,
+ .pcm_new = sprd_pcm_new,
+ .pcm_free = sprd_pcm_free_dma_buffers,
+};
+
+static int sprd_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &sprd_soc_platform);
+}
+
+static int sprd_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sprd_pcm_of_match[] = {
+ {.compatible = "sprd,sprd-pcm",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, sprd_pcm_of_match);
+#endif
+
+static struct platform_driver sprd_pcm_driver = {
+ .driver = {
+ .name = "sprd-pcm-audio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_pcm_of_match),
+ },
+
+ .probe = sprd_soc_platform_probe,
+ .remove = sprd_soc_platform_remove,
+};
+
+module_platform_driver(sprd_pcm_driver);
+
+MODULE_DESCRIPTION("SPRD ASoC PCM DMA");
+MODULE_AUTHOR("Jian chen <jian.chen@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sprd-audio");