diff options
Diffstat (limited to 'sound/soc/sprd/dai/sprd-pcm.c')
| -rw-r--r-- | sound/soc/sprd/dai/sprd-pcm.c | 1025 |
1 files changed, 1025 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/sprd-pcm.c b/sound/soc/sprd/dai/sprd-pcm.c new file mode 100644 index 00000000..517b588c --- /dev/null +++ b/sound/soc/sprd/dai/sprd-pcm.c @@ -0,0 +1,1025 @@ +/* + * sound/soc/sprd/dai/sprd-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/dma.h> +#include <soc/sprd/dma_reg.h> +#include <soc/sprd/sprd-audio.h> + +#include "sprd-asoc-common.h" +#include "sprd-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(sprd_dma_desc)) +#endif + +struct sprd_runtime_data { + int dma_addr_offset; + struct sprd_pcm_dma_params *params; + int uid_cid_map[2]; + int int_pos_update[2]; + sprd_dma_desc *dma_cfg_array; + dma_addr_t *dma_desc_array_orig; + dma_addr_t dma_desc_array_phys_orig; + dma_addr_t *dma_desc_array; + dma_addr_t dma_desc_array_phys; + int burst_len; + int hw_chan; + int dma_pos_pre[2]; + int interleaved; + int irq_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_desc_array_orig = + dma_alloc_coherent(substream->pcm->card->dev, + hw_chan * (PAGE_SIZE+32), + &rtd->dma_desc_array_phys_orig, + GFP_KERNEL); + + rtd->dma_desc_array_phys = (rtd->dma_desc_array_phys_orig+31)&(~31); + rtd->dma_desc_array = (unsigned int)rtd->dma_desc_array_orig + (rtd->dma_desc_array_phys - rtd->dma_desc_array_phys_orig); + + 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->dma_desc_array = + (void *)(s_iram_remap_base + runtime->hw.buffer_bytes_max); + rtd->dma_desc_array_phys = + SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max; + rtd->dma_desc_array_orig = rtd->dma_desc_array; + rtd->dma_desc_array_phys_orig = rtd->dma_desc_array_phys; + rtd->buffer_in_iram = 1; + /*must clear the dma_desc_array first here */ + memset(rtd->dma_desc_array, 0, (2 * SPRD_AUDIO_DMA_NODE_SIZE)); + } +#endif + + if (!rtd->dma_desc_array_orig) + goto err1; + + rtd->dma_cfg_array = + kzalloc(hw_chan * runtime->hw.periods_max * sizeof(sprd_dma_desc), + GFP_KERNEL); + if (!rtd->dma_cfg_array) + goto err2; + + rtd->uid_cid_map[0] = rtd->uid_cid_map[1] = -1; + + rtd->burst_len = burst_len; + rtd->hw_chan = hw_chan; + + runtime->private_data = rtd; + ret = 0; + goto out; + +err2: + pr_err("ERR:dma_cfg_array alloc failed!\n"); + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (rtd->buffer_in_iram) + sprd_buffer_iram_restore(); + else +#endif + dma_free_coherent(substream->pcm->card->dev, + hw_chan * (PAGE_SIZE+32), + rtd->dma_desc_array_orig, + rtd->dma_desc_array_phys_orig); +err1: + pr_err("ERR:dma_desc_array alloc failed!\n"); + 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 + dma_free_coherent(substream->pcm->card->dev, + rtd->hw_chan * (PAGE_SIZE+32), + rtd->dma_desc_array_orig, + rtd->dma_desc_array_phys_orig); + if (!atomic_dec_return(&lightsleep_refcnt)) + sprd_lightsleep_disable("audio", 0); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } +#endif + + kfree(rtd->dma_cfg_array); + kfree(rtd); + + return 0; +} + +static void sprd_pcm_dma_irq_ch(int dma_ch, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + int i = 0; + + if (!rtd->irq_called) { + rtd->irq_called = 1; + sp_asoc_pr_info("IRQ CALL\n"); + } + + if (rtd->hw_chan == 1) + goto irq_fast; + + for (i = 0; i < 2; i++) { + if (dma_ch == rtd->uid_cid_map[i]) { + rtd->int_pos_update[i] = 1; + + if (rtd->uid_cid_map[1 - i] >= 0) { + 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(dev_id); +irq_ret: + return; +} + +/* + * proc interface + */ + +#ifdef CONFIG_SND_VERBOSE_PROCFS +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; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + sprd_dma_desc *dma_desc[2]; + dma_addr_t dma_buff_phys[2]; /*, next_desc_phys[2]; */ + struct i2s_config *config = NULL; + int ret = 0; + int i, j = 0; + int used_chan_count; + int chan_id; + struct reg_cfg_addr dma_reg_addr[2]; + + sp_asoc_pr_dbg("%s\n", __func__); + + dma = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream); + if (!dma) + 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->desc.src_step = 4; + } else { + dma->desc.des_step = 4; + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma->desc.src_step = 2; + } else { + dma->desc.des_step = 2; + } + } + } + + /* this may get called several times by oss emulation + * with different params */ + if (rtd->params == NULL) { + rtd->params = dma; + for (i = 0; i < used_chan_count; i++) { + chan_id = sci_dma_request(dma->name, FULL_DMA_CHN); + if (chan_id < 0) { + pr_err("ERR:PCM Request DMA Error %d\n", + dma->channels[i]); + for (i--; i >= 0; i--) { + sci_dma_free(rtd->uid_cid_map[i]); + rtd->uid_cid_map[i] = -1; + rtd->params = NULL; + } + goto hw_param_err; + } + rtd->uid_cid_map[i] = chan_id; + sp_asoc_pr_dbg("Chan%d DMA ID=%d\n", + rtd->uid_cid_map[i], + rtd->params->channels[i]); + } + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totsize; + + dma_desc[0] = rtd->dma_cfg_array; + dma_desc[1] = rtd->dma_cfg_array + runtime->hw.periods_max; + + dma_buff_phys[0] = runtime->dma_addr; + rtd->dma_addr_offset = (totsize / used_chan_count); + if (sprd_pcm_is_interleaved(runtime)) + rtd->dma_addr_offset = 2; + dma_buff_phys[1] = runtime->dma_addr + rtd->dma_addr_offset; + + do { + for (i = 0; i < used_chan_count; i++) { + dma_desc[i]->datawidth = dma->desc.datawidth; + if (sprd_is_i2s(srtd->cpu_dai)) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + dma_desc[i]->fragmens_len = + (I2S_FIFO_DEPTH - + config->tx_watermark) * + dma_desc[i]->datawidth; + } else { + dma_desc[i]->fragmens_len = + config->rx_watermark * + dma_desc[i]->datawidth; + } + } else { + dma_desc[i]->fragmens_len = + dma->desc.fragmens_len; + } + dma_desc[i]->block_len = period / used_chan_count; + dma_desc[i]->transcation_len = 0; + dma_desc[i]->req_mode = FRAG_REQ_MODE; + dma_desc[i]->src_step = dma->desc.src_step; + dma_desc[i]->des_step = dma->desc.des_step; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_desc[i]->src_addr = dma_buff_phys[i]; + dma_desc[i]->des_addr = dma->dev_paddr[i]; + } else { + dma_desc[i]->src_addr = dma->dev_paddr[i]; + dma_desc[i]->des_addr = dma_buff_phys[i]; + } + + dma_buff_phys[i] += dma_desc[i]->block_len; + if (rtd->interleaved) + dma_buff_phys[i] += dma_desc[i]->block_len; + dma_desc[i]++; + } + + if (period > totsize) + period = totsize; + j++; + } while (totsize -= period); + + sp_asoc_pr_dbg("Node Size:%d\n", j); + + dma_reg_addr[0].phys_addr = (u32) (rtd->dma_desc_array_phys); + dma_reg_addr[0].virt_addr = rtd->dma_desc_array; + sp_asoc_pr_dbg("dma_reg_addr[0].virt_addr:0x%x\n", + dma_reg_addr[0].virt_addr); + sp_asoc_pr_dbg("dma_reg_addr[0].phys_addr:0x%x\n", + dma_reg_addr[0].phys_addr); + + sci_dma_config((u32) (rtd->uid_cid_map[0]), + (struct sci_dma_cfg *)(rtd->dma_cfg_array), + params_periods(params), &dma_reg_addr[0]); + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("Register IRQ for DMA chan ID %d\n", + rtd->uid_cid_map[0]); + ret = + sci_dma_register_irqhandle(rtd->uid_cid_map[0], + rtd->params->irq_type, + sprd_pcm_dma_irq_ch, substream); + } + + if (used_chan_count > 1) { + dma_reg_addr[1].phys_addr = + (u32) (dma_reg_addr[0].phys_addr) + + runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE; + dma_reg_addr[1].virt_addr = + (dma_reg_addr[0].virt_addr) + + runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE; + sp_asoc_pr_dbg("dma_reg_addr[1].virt_addr:0x%x\n", + dma_reg_addr[1].virt_addr); + sp_asoc_pr_dbg("dma_reg_addr[1].phys_addr:0x%x\n", + dma_reg_addr[1].phys_addr); + + sci_dma_config((u32) (rtd->uid_cid_map[1]), + (struct sci_dma_cfg *)(rtd->dma_cfg_array + + runtime->hw.periods_max), + params_periods(params), &dma_reg_addr[1]); + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("Register IRQ for DMA Chan ID %d\n", + rtd->uid_cid_map[1]); + ret = + sci_dma_register_irqhandle(rtd->uid_cid_map[1], + rtd->params->irq_type, + sprd_pcm_dma_irq_ch, + substream); + } + + } + + 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->uid_cid_map[i] >= 0) { + sci_dma_free(rtd->uid_cid_map[i]); + rtd->uid_cid_map[i] = -1; + } + } + 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->uid_cid_map[i] >= 0) { + sci_dma_start(rtd->uid_cid_map[i], + dma->channels[i]); + } + } + 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->uid_cid_map[i] >= 0) { + sci_dma_stop(rtd->uid_cid_map[i], + dma->channels[i]); + } + } + rtd->irq_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->uid_cid_map[0] >= 0) { + now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[0], + substream) - + runtime->dma_addr; + bytes_of_pointer = now_pointer; + } + if (rtd->uid_cid_map[1] >= 0) { + now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[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_free_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, +}; + +static int __init sprd_pcm_driver_init(void) +{ + return platform_driver_register(&sprd_pcm_driver); +} + +late_initcall(sprd_pcm_driver_init); + + +MODULE_DESCRIPTION("SPRD ASoC PCM DMA"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sprd-audio"); |
