1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC. 4 * 5 * Copyright (C) 2012 Atmel 6 * 7 * Author: Bo Shen <voice.shen@atmel.com> 8 * 9 * Based on atmel-pcm by: 10 * Sedji Gaouaou <sedji.gaouaou@atmel.com> 11 * Copyright 2008 Atmel 12 */ 13 14 #include <linux/module.h> 15 #include <linux/init.h> 16 #include <linux/platform_device.h> 17 #include <linux/slab.h> 18 #include <linux/dma-mapping.h> 19 #include <linux/dmaengine.h> 20 #include <linux/atmel-ssc.h> 21 22 #include <sound/core.h> 23 #include <sound/pcm.h> 24 #include <sound/pcm_params.h> 25 #include <sound/soc.h> 26 #include <sound/dmaengine_pcm.h> 27 28 #include "atmel-pcm.h" 29 30 /*--------------------------------------------------------------------------*\ 31 * Hardware definition 32 \*--------------------------------------------------------------------------*/ 33 static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { 34 .info = SNDRV_PCM_INFO_MMAP | 35 SNDRV_PCM_INFO_MMAP_VALID | 36 SNDRV_PCM_INFO_INTERLEAVED | 37 SNDRV_PCM_INFO_RESUME | 38 SNDRV_PCM_INFO_PAUSE, 39 .period_bytes_min = 256, /* lighting DMA overhead */ 40 .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ 41 .periods_min = 8, 42 .periods_max = 1024, /* no limit */ 43 .buffer_bytes_max = 512 * 1024, 44 }; 45 46 /* 47 * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC 48 * 49 * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to 50 * check if any overrun occured. 51 */ 52 static void atmel_pcm_dma_irq(u32 ssc_sr, 53 struct snd_pcm_substream *substream) 54 { 55 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 56 struct atmel_pcm_dma_params *prtd; 57 58 prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); 59 60 if (ssc_sr & prtd->mask->ssc_error) { 61 if (snd_pcm_running(substream)) 62 pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n", 63 substream->stream == SNDRV_PCM_STREAM_PLAYBACK 64 ? "underrun" : "overrun", prtd->name, 65 ssc_sr); 66 67 /* stop RX and capture: will be enabled again at restart */ 68 ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable); 69 snd_pcm_stop_xrun(substream); 70 71 /* now drain RHR and read status to remove xrun condition */ 72 ssc_readx(prtd->ssc->regs, SSC_RHR); 73 ssc_readx(prtd->ssc->regs, SSC_SR); 74 } 75 } 76 77 static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, 78 struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config) 79 { 80 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 81 struct atmel_pcm_dma_params *prtd; 82 struct ssc_device *ssc; 83 int ret; 84 85 prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); 86 ssc = prtd->ssc; 87 88 ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); 89 if (ret) { 90 pr_err("atmel-pcm: hwparams to dma slave configure failed\n"); 91 return ret; 92 } 93 94 slave_config->dst_addr = ssc->phybase + SSC_THR; 95 slave_config->dst_maxburst = 1; 96 97 slave_config->src_addr = ssc->phybase + SSC_RHR; 98 slave_config->src_maxburst = 1; 99 100 prtd->dma_intr_handler = atmel_pcm_dma_irq; 101 102 return 0; 103 } 104 105 static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = { 106 .prepare_slave_config = atmel_pcm_configure_dma, 107 .pcm_hardware = &atmel_pcm_dma_hardware, 108 .prealloc_buffer_size = 64 * 1024, 109 }; 110 111 int atmel_pcm_dma_platform_register(struct device *dev) 112 { 113 return devm_snd_dmaengine_pcm_register(dev, 114 &atmel_dmaengine_pcm_config, 0); 115 } 116 EXPORT_SYMBOL(atmel_pcm_dma_platform_register); 117 118 MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); 119 MODULE_DESCRIPTION("Atmel DMA based PCM module"); 120 MODULE_LICENSE("GPL"); 121