1 /* 2 * atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC. 3 * 4 * Copyright (C) 2012 Atmel 5 * 6 * Author: Bo Shen <voice.shen@atmel.com> 7 * 8 * Based on atmel-pcm by: 9 * Sedji Gaouaou <sedji.gaouaou@atmel.com> 10 * Copyright 2008 Atmel 11 * 12 * This program is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU General Public License as published by 14 * the Free Software Foundation; either version 2 of the License, or 15 * (at your option) any later version. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU General Public License for more details. 21 * 22 * You should have received a copy of the GNU General Public License 23 * along with this program; if not, write to the Free Software 24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25 */ 26 27 #include <linux/module.h> 28 #include <linux/init.h> 29 #include <linux/platform_device.h> 30 #include <linux/slab.h> 31 #include <linux/dma-mapping.h> 32 #include <linux/dmaengine.h> 33 #include <linux/atmel-ssc.h> 34 #include <linux/platform_data/dma-atmel.h> 35 36 #include <sound/core.h> 37 #include <sound/pcm.h> 38 #include <sound/pcm_params.h> 39 #include <sound/soc.h> 40 #include <sound/dmaengine_pcm.h> 41 42 #include "atmel-pcm.h" 43 44 /*--------------------------------------------------------------------------*\ 45 * Hardware definition 46 \*--------------------------------------------------------------------------*/ 47 static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { 48 .info = SNDRV_PCM_INFO_MMAP | 49 SNDRV_PCM_INFO_MMAP_VALID | 50 SNDRV_PCM_INFO_INTERLEAVED | 51 SNDRV_PCM_INFO_RESUME | 52 SNDRV_PCM_INFO_PAUSE, 53 .formats = SNDRV_PCM_FMTBIT_S16_LE, 54 .period_bytes_min = 256, /* lighting DMA overhead */ 55 .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ 56 .periods_min = 8, 57 .periods_max = 1024, /* no limit */ 58 .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, 59 }; 60 61 /** 62 * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC 63 * 64 * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to 65 * check if any overrun occured. 66 */ 67 static void atmel_pcm_dma_irq(u32 ssc_sr, 68 struct snd_pcm_substream *substream) 69 { 70 struct snd_soc_pcm_runtime *rtd = substream->private_data; 71 struct atmel_pcm_dma_params *prtd; 72 73 prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 74 75 if (ssc_sr & prtd->mask->ssc_error) { 76 if (snd_pcm_running(substream)) 77 pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n", 78 substream->stream == SNDRV_PCM_STREAM_PLAYBACK 79 ? "underrun" : "overrun", prtd->name, 80 ssc_sr); 81 82 /* stop RX and capture: will be enabled again at restart */ 83 ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable); 84 snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); 85 86 /* now drain RHR and read status to remove xrun condition */ 87 ssc_readx(prtd->ssc->regs, SSC_RHR); 88 ssc_readx(prtd->ssc->regs, SSC_SR); 89 } 90 } 91 92 /*--------------------------------------------------------------------------*\ 93 * DMAENGINE operations 94 \*--------------------------------------------------------------------------*/ 95 static bool filter(struct dma_chan *chan, void *slave) 96 { 97 struct at_dma_slave *sl = slave; 98 99 if (sl->dma_dev == chan->device->dev) { 100 chan->private = sl; 101 return true; 102 } else { 103 return false; 104 } 105 } 106 107 static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, 108 struct snd_pcm_hw_params *params, struct atmel_pcm_dma_params *prtd) 109 { 110 struct ssc_device *ssc; 111 struct dma_chan *dma_chan; 112 struct dma_slave_config slave_config; 113 int ret; 114 115 ssc = prtd->ssc; 116 117 ret = snd_hwparams_to_dma_slave_config(substream, params, 118 &slave_config); 119 if (ret) { 120 pr_err("atmel-pcm: hwparams to dma slave configure failed\n"); 121 return ret; 122 } 123 124 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 125 slave_config.dst_addr = (dma_addr_t)ssc->phybase + SSC_THR; 126 slave_config.dst_maxburst = 1; 127 } else { 128 slave_config.src_addr = (dma_addr_t)ssc->phybase + SSC_RHR; 129 slave_config.src_maxburst = 1; 130 } 131 132 dma_chan = snd_dmaengine_pcm_get_chan(substream); 133 if (dmaengine_slave_config(dma_chan, &slave_config)) { 134 pr_err("atmel-pcm: failed to configure dma channel\n"); 135 ret = -EBUSY; 136 return ret; 137 } 138 139 return 0; 140 } 141 142 static int atmel_pcm_hw_params(struct snd_pcm_substream *substream, 143 struct snd_pcm_hw_params *params) 144 { 145 struct snd_soc_pcm_runtime *rtd = substream->private_data; 146 struct atmel_pcm_dma_params *prtd; 147 struct ssc_device *ssc; 148 struct at_dma_slave *sdata = NULL; 149 int ret; 150 151 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 152 153 prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 154 ssc = prtd->ssc; 155 if (ssc->pdev) 156 sdata = ssc->pdev->dev.platform_data; 157 158 ret = snd_dmaengine_pcm_open_request_chan(substream, filter, sdata); 159 if (ret) { 160 pr_err("atmel-pcm: dmaengine pcm open failed\n"); 161 return -EINVAL; 162 } 163 164 ret = atmel_pcm_configure_dma(substream, params, prtd); 165 if (ret) { 166 pr_err("atmel-pcm: failed to configure dmai\n"); 167 goto err; 168 } 169 170 prtd->dma_intr_handler = atmel_pcm_dma_irq; 171 172 return 0; 173 err: 174 snd_dmaengine_pcm_close_release_chan(substream); 175 return ret; 176 } 177 178 static int atmel_pcm_dma_prepare(struct snd_pcm_substream *substream) 179 { 180 struct snd_soc_pcm_runtime *rtd = substream->private_data; 181 struct atmel_pcm_dma_params *prtd; 182 183 prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 184 185 ssc_writex(prtd->ssc->regs, SSC_IER, prtd->mask->ssc_error); 186 ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_enable); 187 188 return 0; 189 } 190 191 static int atmel_pcm_open(struct snd_pcm_substream *substream) 192 { 193 snd_soc_set_runtime_hwparams(substream, &atmel_pcm_dma_hardware); 194 195 return 0; 196 } 197 198 static struct snd_pcm_ops atmel_pcm_ops = { 199 .open = atmel_pcm_open, 200 .close = snd_dmaengine_pcm_close_release_chan, 201 .ioctl = snd_pcm_lib_ioctl, 202 .hw_params = atmel_pcm_hw_params, 203 .prepare = atmel_pcm_dma_prepare, 204 .trigger = snd_dmaengine_pcm_trigger, 205 .pointer = snd_dmaengine_pcm_pointer_no_residue, 206 .mmap = atmel_pcm_mmap, 207 }; 208 209 static struct snd_soc_platform_driver atmel_soc_platform = { 210 .ops = &atmel_pcm_ops, 211 .pcm_new = atmel_pcm_new, 212 .pcm_free = atmel_pcm_free, 213 }; 214 215 int atmel_pcm_dma_platform_register(struct device *dev) 216 { 217 return snd_soc_register_platform(dev, &atmel_soc_platform); 218 } 219 EXPORT_SYMBOL(atmel_pcm_dma_platform_register); 220 221 void atmel_pcm_dma_platform_unregister(struct device *dev) 222 { 223 snd_soc_unregister_platform(dev); 224 } 225 EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister); 226 227 MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); 228 MODULE_DESCRIPTION("Atmel DMA based PCM module"); 229 MODULE_LICENSE("GPL"); 230