1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. 4 * 5 * Copyright (C) 2005 SAN People 6 * Copyright (C) 2008 Atmel 7 * 8 * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> 9 * 10 * Based on at91-pcm. by: 11 * Frank Mandarino <fmandarino@endrelia.com> 12 * Copyright 2006 Endrelia Technologies Inc. 13 * 14 * Based on pxa2xx-pcm.c by: 15 * 16 * Author: Nicolas Pitre 17 * Created: Nov 30, 2004 18 * Copyright: (C) 2004 MontaVista Software, Inc. 19 */ 20 21 #include <linux/module.h> 22 #include <linux/init.h> 23 #include <linux/platform_device.h> 24 #include <linux/slab.h> 25 #include <linux/dma-mapping.h> 26 #include <linux/atmel_pdc.h> 27 #include <linux/atmel-ssc.h> 28 29 #include <sound/core.h> 30 #include <sound/pcm.h> 31 #include <sound/pcm_params.h> 32 #include <sound/soc.h> 33 34 #include "atmel-pcm.h" 35 36 37 static int atmel_pcm_new(struct snd_soc_component *component, 38 struct snd_soc_pcm_runtime *rtd) 39 { 40 struct snd_card *card = rtd->card->snd_card; 41 int ret; 42 43 ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 44 if (ret) 45 return ret; 46 47 snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, 48 card->dev, ATMEL_SSC_DMABUF_SIZE, 49 ATMEL_SSC_DMABUF_SIZE); 50 51 return 0; 52 } 53 54 /*--------------------------------------------------------------------------*\ 55 * Hardware definition 56 \*--------------------------------------------------------------------------*/ 57 /* TODO: These values were taken from the AT91 platform driver, check 58 * them against real values for AT32 59 */ 60 static const struct snd_pcm_hardware atmel_pcm_hardware = { 61 .info = SNDRV_PCM_INFO_MMAP | 62 SNDRV_PCM_INFO_MMAP_VALID | 63 SNDRV_PCM_INFO_INTERLEAVED | 64 SNDRV_PCM_INFO_PAUSE, 65 .period_bytes_min = 32, 66 .period_bytes_max = 8192, 67 .periods_min = 2, 68 .periods_max = 1024, 69 .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, 70 }; 71 72 73 /*--------------------------------------------------------------------------*\ 74 * Data types 75 \*--------------------------------------------------------------------------*/ 76 struct atmel_runtime_data { 77 struct atmel_pcm_dma_params *params; 78 dma_addr_t dma_buffer; /* physical address of dma buffer */ 79 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ 80 size_t period_size; 81 82 dma_addr_t period_ptr; /* physical address of next period */ 83 }; 84 85 /*--------------------------------------------------------------------------*\ 86 * ISR 87 \*--------------------------------------------------------------------------*/ 88 static void atmel_pcm_dma_irq(u32 ssc_sr, 89 struct snd_pcm_substream *substream) 90 { 91 struct atmel_runtime_data *prtd = substream->runtime->private_data; 92 struct atmel_pcm_dma_params *params = prtd->params; 93 static int count; 94 95 count++; 96 97 if (ssc_sr & params->mask->ssc_endbuf) { 98 pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", 99 substream->stream == SNDRV_PCM_STREAM_PLAYBACK 100 ? "underrun" : "overrun", 101 params->name, ssc_sr, count); 102 103 /* re-start the PDC */ 104 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 105 params->mask->pdc_disable); 106 prtd->period_ptr += prtd->period_size; 107 if (prtd->period_ptr >= prtd->dma_buffer_end) 108 prtd->period_ptr = prtd->dma_buffer; 109 110 ssc_writex(params->ssc->regs, params->pdc->xpr, 111 prtd->period_ptr); 112 ssc_writex(params->ssc->regs, params->pdc->xcr, 113 prtd->period_size / params->pdc_xfer_size); 114 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 115 params->mask->pdc_enable); 116 } 117 118 if (ssc_sr & params->mask->ssc_endx) { 119 /* Load the PDC next pointer and counter registers */ 120 prtd->period_ptr += prtd->period_size; 121 if (prtd->period_ptr >= prtd->dma_buffer_end) 122 prtd->period_ptr = prtd->dma_buffer; 123 124 ssc_writex(params->ssc->regs, params->pdc->xnpr, 125 prtd->period_ptr); 126 ssc_writex(params->ssc->regs, params->pdc->xncr, 127 prtd->period_size / params->pdc_xfer_size); 128 } 129 130 snd_pcm_period_elapsed(substream); 131 } 132 133 134 /*--------------------------------------------------------------------------*\ 135 * PCM operations 136 \*--------------------------------------------------------------------------*/ 137 static int atmel_pcm_hw_params(struct snd_soc_component *component, 138 struct snd_pcm_substream *substream, 139 struct snd_pcm_hw_params *params) 140 { 141 struct snd_pcm_runtime *runtime = substream->runtime; 142 struct atmel_runtime_data *prtd = runtime->private_data; 143 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 144 145 /* this may get called several times by oss emulation 146 * with different params */ 147 148 prtd->params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); 149 prtd->params->dma_intr_handler = atmel_pcm_dma_irq; 150 151 prtd->dma_buffer = runtime->dma_addr; 152 prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; 153 prtd->period_size = params_period_bytes(params); 154 155 pr_debug("atmel-pcm: " 156 "hw_params: DMA for %s initialized " 157 "(dma_bytes=%zu, period_size=%zu)\n", 158 prtd->params->name, 159 runtime->dma_bytes, 160 prtd->period_size); 161 return 0; 162 } 163 164 static int atmel_pcm_hw_free(struct snd_soc_component *component, 165 struct snd_pcm_substream *substream) 166 { 167 struct atmel_runtime_data *prtd = substream->runtime->private_data; 168 struct atmel_pcm_dma_params *params = prtd->params; 169 170 if (params != NULL) { 171 ssc_writex(params->ssc->regs, SSC_PDC_PTCR, 172 params->mask->pdc_disable); 173 prtd->params->dma_intr_handler = NULL; 174 } 175 176 return 0; 177 } 178 179 static int atmel_pcm_prepare(struct snd_soc_component *component, 180 struct snd_pcm_substream *substream) 181 { 182 struct atmel_runtime_data *prtd = substream->runtime->private_data; 183 struct atmel_pcm_dma_params *params = prtd->params; 184 185 ssc_writex(params->ssc->regs, SSC_IDR, 186 params->mask->ssc_endx | params->mask->ssc_endbuf); 187 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 188 params->mask->pdc_disable); 189 return 0; 190 } 191 192 static int atmel_pcm_trigger(struct snd_soc_component *component, 193 struct snd_pcm_substream *substream, int cmd) 194 { 195 struct snd_pcm_runtime *rtd = substream->runtime; 196 struct atmel_runtime_data *prtd = rtd->private_data; 197 struct atmel_pcm_dma_params *params = prtd->params; 198 int ret = 0; 199 200 pr_debug("atmel-pcm:buffer_size = %ld," 201 "dma_area = %p, dma_bytes = %zu\n", 202 rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); 203 204 switch (cmd) { 205 case SNDRV_PCM_TRIGGER_START: 206 prtd->period_ptr = prtd->dma_buffer; 207 208 ssc_writex(params->ssc->regs, params->pdc->xpr, 209 prtd->period_ptr); 210 ssc_writex(params->ssc->regs, params->pdc->xcr, 211 prtd->period_size / params->pdc_xfer_size); 212 213 prtd->period_ptr += prtd->period_size; 214 ssc_writex(params->ssc->regs, params->pdc->xnpr, 215 prtd->period_ptr); 216 ssc_writex(params->ssc->regs, params->pdc->xncr, 217 prtd->period_size / params->pdc_xfer_size); 218 219 pr_debug("atmel-pcm: trigger: " 220 "period_ptr=%lx, xpr=%u, " 221 "xcr=%u, xnpr=%u, xncr=%u\n", 222 (unsigned long)prtd->period_ptr, 223 ssc_readx(params->ssc->regs, params->pdc->xpr), 224 ssc_readx(params->ssc->regs, params->pdc->xcr), 225 ssc_readx(params->ssc->regs, params->pdc->xnpr), 226 ssc_readx(params->ssc->regs, params->pdc->xncr)); 227 228 ssc_writex(params->ssc->regs, SSC_IER, 229 params->mask->ssc_endx | params->mask->ssc_endbuf); 230 ssc_writex(params->ssc->regs, SSC_PDC_PTCR, 231 params->mask->pdc_enable); 232 233 pr_debug("sr=%u imr=%u\n", 234 ssc_readx(params->ssc->regs, SSC_SR), 235 ssc_readx(params->ssc->regs, SSC_IER)); 236 break; /* SNDRV_PCM_TRIGGER_START */ 237 238 case SNDRV_PCM_TRIGGER_STOP: 239 case SNDRV_PCM_TRIGGER_SUSPEND: 240 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 241 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 242 params->mask->pdc_disable); 243 break; 244 245 case SNDRV_PCM_TRIGGER_RESUME: 246 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 247 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 248 params->mask->pdc_enable); 249 break; 250 251 default: 252 ret = -EINVAL; 253 } 254 255 return ret; 256 } 257 258 static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component, 259 struct snd_pcm_substream *substream) 260 { 261 struct snd_pcm_runtime *runtime = substream->runtime; 262 struct atmel_runtime_data *prtd = runtime->private_data; 263 struct atmel_pcm_dma_params *params = prtd->params; 264 dma_addr_t ptr; 265 snd_pcm_uframes_t x; 266 267 ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); 268 x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); 269 270 if (x == runtime->buffer_size) 271 x = 0; 272 273 return x; 274 } 275 276 static int atmel_pcm_open(struct snd_soc_component *component, 277 struct snd_pcm_substream *substream) 278 { 279 struct snd_pcm_runtime *runtime = substream->runtime; 280 struct atmel_runtime_data *prtd; 281 int ret = 0; 282 283 snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware); 284 285 /* ensure that buffer size is a multiple of period size */ 286 ret = snd_pcm_hw_constraint_integer(runtime, 287 SNDRV_PCM_HW_PARAM_PERIODS); 288 if (ret < 0) 289 goto out; 290 291 prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL); 292 if (prtd == NULL) { 293 ret = -ENOMEM; 294 goto out; 295 } 296 runtime->private_data = prtd; 297 298 out: 299 return ret; 300 } 301 302 static int atmel_pcm_close(struct snd_soc_component *component, 303 struct snd_pcm_substream *substream) 304 { 305 struct atmel_runtime_data *prtd = substream->runtime->private_data; 306 307 kfree(prtd); 308 return 0; 309 } 310 311 static const struct snd_soc_component_driver atmel_soc_platform = { 312 .open = atmel_pcm_open, 313 .close = atmel_pcm_close, 314 .hw_params = atmel_pcm_hw_params, 315 .hw_free = atmel_pcm_hw_free, 316 .prepare = atmel_pcm_prepare, 317 .trigger = atmel_pcm_trigger, 318 .pointer = atmel_pcm_pointer, 319 .pcm_construct = atmel_pcm_new, 320 }; 321 322 int atmel_pcm_pdc_platform_register(struct device *dev) 323 { 324 return devm_snd_soc_register_component(dev, &atmel_soc_platform, 325 NULL, 0); 326 } 327 EXPORT_SYMBOL(atmel_pcm_pdc_platform_register); 328 329 MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); 330 MODULE_DESCRIPTION("Atmel PCM module"); 331 MODULE_LICENSE("GPL"); 332
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.