//#define MULTICORE_DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #define DMA_CHAIN_LEN 2 static struct snd_pcm_hardware pcm_hardware_playback = { .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH), .formats = MC_PCM_FMTBITS, .rates = MC_PCM_RATES, .rate_min = 5512, .rate_max = 192000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128 * 1024, //.period_bytes_min = 16, //.period_bytes_max = 512 * 1024, .period_bytes_min = 32768, .period_bytes_max = 32768, .periods_min = DMA_CHAIN_LEN, .periods_max = DMA_CHAIN_LEN, .fifo_size = 0, }; static struct snd_pcm_hardware pcm_hardware_capture = { .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH), .formats = MC_PCM_FMTBITS, .rates = MC_PCM_RATES, .rate_min = 5512, .rate_max = 192000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128 * 1024, //.period_bytes_min = 16, //.period_bytes_max = 512 * 1024, .period_bytes_min = 32768, .period_bytes_max = 32768, .periods_min = DMA_CHAIN_LEN, .periods_max = DMA_CHAIN_LEN, .fifo_size = 0, }; static inline u32 _R(struct snd_pcm_substream *substream, unsigned idx) { struct mc_dma_private_data *dma_prtd = substream->dma_buffer.private_data; return __raw_readl(dma_prtd->dma_regs + idx); } static inline void _W(struct snd_pcm_substream *substream, unsigned idx, u32 val) { struct mc_dma_private_data *dma_prtd = substream->dma_buffer.private_data; __raw_writel(val, dma_prtd->dma_regs + idx); } static inline void hw_clear_interrupt(struct snd_pcm_substream *substream) { (void)_R(substream, RG_DMA_CSR); } static inline int mc_get_mfbsp_port(struct snd_pcm_substream *substream) { struct mc_dma_private_data *dma_prtd = substream->dma_buffer.private_data; return dma_prtd->iisc->port; } static irqreturn_t mc_pcm_interrupt(int irq, void *dev_id) { struct snd_pcm_substream *substream = dev_id; PDEBUG("%s\n", __func__); hw_clear_interrupt(substream); snd_pcm_period_elapsed(substream); return IRQ_HANDLED; } static int mc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = 0; PDEBUG("Enter %s\n", __func__); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: PDEBUG("START DMA: CP = %08X, CSR = %08X, IR = %08X\n", _R(substream, RG_DMA_CP), _R(substream, RG_DMA_CSR), _R(substream, RG_DMA_IR)); _W(substream, RG_DMA_CP, _R(substream, RG_DMA_CP) | 1); PDEBUG("START DMA: CP = %08X, CSR = %08Xmcp, IR = %08X\n", _R(substream, RG_DMA_CP), _R(substream, RG_DMA_CSR), _R(substream, RG_DMA_IR)); PDEBUG("CP_chain = %08X, CSR_chain = %08X, IR_chain = %08X\n", *((unsigned *) 0xa3e0c008), *((unsigned *) 0xa3e0c00c), *((unsigned *) 0xa3e0c004)); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: _W(substream, RG_DMA_RUN, 0); while (_R(substream, RG_DMA_CSR) & 1); break; default: ret = -EINVAL; break; } PDEBUG("Exit %s\n", __func__); return ret; } static snd_pcm_uframes_t mc_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; unsigned dma_buf = (unsigned)substream->dma_buffer.addr; PDEBUG("Enter %s, IR = %08X\n", __func__, _R(substream, RG_DMA_IR)); return bytes_to_frames(runtime, _R(substream, RG_DMA_IR) - dma_buf); } static int mc_pcm_prepare(struct snd_pcm_substream *substream) { struct mc_dma_private_data *dma_prtd = substream->dma_buffer.private_data; dma_params_t *chain = dma_prtd->dma_chain; dma_addr_t chain_phys = dma_prtd->dma_chain_phys; struct snd_pcm_runtime *runtime = substream->runtime; unsigned period_bytes = snd_pcm_lib_period_bytes(substream); unsigned dma_buf = (unsigned)substream->dma_buffer.addr; int i; PDEBUG("Enter %s, dma_buf = %08X\n", __func__, dma_buf); if (runtime->periods != DMA_CHAIN_LEN) { PDEBUG("mc_pcm: periods = %d\n", runtime->periods); return -EINVAL; } if (period_bytes & 0x7) { PDEBUG("mc_pcm: period_bytes = %d, not aligned to 8\n", period_bytes); return -EINVAL; } dma_prtd->cur_period = 0; // Инициализация цепочек самоинициализации DMA for (i = 0; i < DMA_CHAIN_LEN; ++i) { chain[i].ir = (unsigned)dma_buf + i * period_bytes; chain[i].csr = F_DMA_IM | F_DMA_CHEN | F_DMA_WN(0) | F_DMA_WCX((period_bytes >> 3) - 1) | F_DMA_RUN; chain[i].cp = chain_phys + sizeof(dma_params_t) * (i+1); PDEBUG("CSR = %08X, IR = %08X, CP = %08X, CSR @ %p, IR @ %p, CP @ %p\n", chain[i].csr, chain[i].ir, chain[i].cp, &chain[i].csr, &chain[i].ir, &chain[i].cp); } chain[i - 1].csr = F_DMA_IM | F_DMA_CHEN | F_DMA_WN(0) | F_DMA_WCX((period_bytes >> 3) - 1) | F_DMA_RUN; chain[i - 1].cp = chain_phys; _W(substream, RG_DMA_CP, dma_prtd->dma_chain_phys); PDEBUG("Exit %s\n", __func__); return 0; } static int mc_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *ppcm; int port = mc_get_mfbsp_port(substream); int ret = 0; PDEBUG("Enter %s\n", __func__); ppcm = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? &pcm_hardware_playback : &pcm_hardware_capture; snd_soc_set_runtime_hwparams(substream, ppcm); /* ensure that buffer size is a multiple of period size */ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) return ret; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = request_irq (mfbsp_dma_tx_irq_num(port), mc_pcm_interrupt, 0, "pcm_tx_dma", substream); } else { ret = request_irq (mfbsp_dma_rx_irq_num(port), mc_pcm_interrupt, 0, "pcm_rx_dma", substream); } if (ret < 0) return ret; PDEBUG("Exit %s\n", __func__); return ret; } static int mc_pcm_close(struct snd_pcm_substream *substream) { int port = mc_get_mfbsp_port(substream); PDEBUG("Enter %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { disable_irq(mfbsp_dma_tx_irq_num(port)); free_irq(mfbsp_dma_tx_irq_num(port), substream); } else { disable_irq(mfbsp_dma_rx_irq_num(port)); free_irq(mfbsp_dma_rx_irq_num(port), substream); } PDEBUG("Exit %s\n", __func__); return 0; } static int mc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } static int mc_pcm_hw_free(struct snd_pcm_substream *substream) { PDEBUG("Enter %s\n", __func__); return snd_pcm_lib_free_pages(substream); } static int mc_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; PDEBUG("Enter %s\n", __func__); return dma_mmap_coherent(substream->pcm->card->dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); } static struct snd_pcm_ops mc_pcm_ops = { .open = mc_pcm_open, .close = mc_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = mc_pcm_hw_params, .hw_free = mc_pcm_hw_free, .prepare = mc_pcm_prepare, .trigger = mc_pcm_trigger, .pointer = mc_pcm_pointer, .mmap = mc_pcm_mmap, }; static int mc_pcm_preallocate_dma_buffer(struct snd_soc_pcm_runtime *rtd, int stream, size_t size) { struct snd_pcm *pcm = rtd->pcm; struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct mc_dma_private_data *dma_priv; struct snd_dma_buffer *buf = &substream->dma_buffer; PDEBUG("Enter %s\n", __func__); buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; dma_priv = devm_kzalloc(pcm->card->dev, sizeof(struct mc_dma_private_data), GFP_KERNEL); if (dma_priv == 0) return -ENOMEM; dma_priv->iisc = snd_soc_dai_get_drvdata(rtd->cpu_dai); PDEBUG("PORT: %d\n", dma_priv->iisc->port); dma_priv->dma_regs = (stream == SNDRV_PCM_STREAM_PLAYBACK) ? dma_priv->iisc->dma_tx_regs : dma_priv->iisc->dma_rx_regs; dma_priv->dma_chain = dmam_alloc_coherent(pcm->card->dev, DMA_CHAIN_LEN * sizeof(dma_params_t), &dma_priv->dma_chain_phys, GFP_KERNEL); buf->private_data = dma_priv; buf->area = dmam_alloc_coherent(pcm->card->dev, size, &buf->addr, GFP_KERNEL); PDEBUG("mc_pcm: preallocate_dma_buffer: area=%p, addr=%p, " "size=%d, dma_chain = %p, chain_phys = %08X\n", (void *) buf->area, (void *) buf->addr, size, dma_priv->dma_chain, dma_priv->dma_chain_phys); if (!buf->area) return -ENOMEM; buf->bytes = size; PDEBUG("Exit %s\n", __func__); return 0; } static void mc_pcm_free(struct snd_pcm *pcm) { PDEBUG("Enter %s\n", __func__); } static u64 mc_pcm_dmamask = DMA_BIT_MASK(32); static int mc_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; struct snd_pcm *pcm = rtd->pcm; int ret; PDEBUG("Enter %s\n", __func__); if (!card->dev->dma_mask) card->dev->dma_mask = &mc_pcm_dmamask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = DMA_BIT_MASK(32); if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { ret = mc_pcm_preallocate_dma_buffer(rtd, SNDRV_PCM_STREAM_PLAYBACK, pcm_hardware_playback.buffer_bytes_max); if (ret) return ret; } if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { ret = mc_pcm_preallocate_dma_buffer(rtd, SNDRV_PCM_STREAM_CAPTURE, pcm_hardware_capture.buffer_bytes_max); if (ret) return ret; } PDEBUG("Exit %s\n", __func__); return 0; } static struct snd_soc_platform_driver mc_soc_platform = { .ops = &mc_pcm_ops, .pcm_new = mc_pcm_new, .pcm_free = mc_pcm_free, }; static int __devinit mc_soc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &mc_soc_platform); } static int __devexit mc_soc_platform_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; } static struct platform_driver mc_pcm_driver = { .driver = { .name = "multicore-audio", .owner = THIS_MODULE, }, .probe = mc_soc_platform_probe, .remove = __devexit_p(mc_soc_platform_remove), }; module_platform_driver(mc_pcm_driver); MODULE_AUTHOR("Dmitry Podkhvatilin"); MODULE_DESCRIPTION("MULTICORE PCM DMA module"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:multicore-audio");