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