11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 292dfa619SBo Shen /* 392dfa619SBo Shen * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. 492dfa619SBo Shen * 592dfa619SBo Shen * Copyright (C) 2005 SAN People 692dfa619SBo Shen * Copyright (C) 2008 Atmel 792dfa619SBo Shen * 892dfa619SBo Shen * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> 992dfa619SBo Shen * 1092dfa619SBo Shen * Based on at91-pcm. by: 1192dfa619SBo Shen * Frank Mandarino <fmandarino@endrelia.com> 1292dfa619SBo Shen * Copyright 2006 Endrelia Technologies Inc. 1392dfa619SBo Shen * 1492dfa619SBo Shen * Based on pxa2xx-pcm.c by: 1592dfa619SBo Shen * 1692dfa619SBo Shen * Author: Nicolas Pitre 1792dfa619SBo Shen * Created: Nov 30, 2004 1892dfa619SBo Shen * Copyright: (C) 2004 MontaVista Software, Inc. 1992dfa619SBo Shen */ 2092dfa619SBo Shen 2192dfa619SBo Shen #include <linux/module.h> 2292dfa619SBo Shen #include <linux/init.h> 2392dfa619SBo Shen #include <linux/platform_device.h> 2492dfa619SBo Shen #include <linux/slab.h> 2592dfa619SBo Shen #include <linux/dma-mapping.h> 2692dfa619SBo Shen #include <linux/atmel_pdc.h> 2792dfa619SBo Shen #include <linux/atmel-ssc.h> 2892dfa619SBo Shen 2992dfa619SBo Shen #include <sound/core.h> 3092dfa619SBo Shen #include <sound/pcm.h> 3192dfa619SBo Shen #include <sound/pcm_params.h> 3292dfa619SBo Shen #include <sound/soc.h> 3392dfa619SBo Shen 3492dfa619SBo Shen #include "atmel-pcm.h" 3592dfa619SBo Shen 3692dfa619SBo Shen 37a94e3f2dSKuninori Morimoto static int atmel_pcm_new(struct snd_soc_component *component, 38a94e3f2dSKuninori Morimoto struct snd_soc_pcm_runtime *rtd) 39488cb533SAlexandre Belloni { 40488cb533SAlexandre Belloni struct snd_card *card = rtd->card->snd_card; 41488cb533SAlexandre Belloni int ret; 42488cb533SAlexandre Belloni 43488cb533SAlexandre Belloni ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 44488cb533SAlexandre Belloni if (ret) 45488cb533SAlexandre Belloni return ret; 46488cb533SAlexandre Belloni 47*22eee4d3SLars-Peter Clausen snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, 48*22eee4d3SLars-Peter Clausen card->dev, ATMEL_SSC_DMABUF_SIZE, 49*22eee4d3SLars-Peter Clausen ATMEL_SSC_DMABUF_SIZE); 50488cb533SAlexandre Belloni 51*22eee4d3SLars-Peter Clausen return 0; 52488cb533SAlexandre Belloni } 53488cb533SAlexandre Belloni 5492dfa619SBo Shen /*--------------------------------------------------------------------------*\ 5592dfa619SBo Shen * Hardware definition 5692dfa619SBo Shen \*--------------------------------------------------------------------------*/ 5792dfa619SBo Shen /* TODO: These values were taken from the AT91 platform driver, check 5892dfa619SBo Shen * them against real values for AT32 5992dfa619SBo Shen */ 6092dfa619SBo Shen static const struct snd_pcm_hardware atmel_pcm_hardware = { 6192dfa619SBo Shen .info = SNDRV_PCM_INFO_MMAP | 6292dfa619SBo Shen SNDRV_PCM_INFO_MMAP_VALID | 6392dfa619SBo Shen SNDRV_PCM_INFO_INTERLEAVED | 6492dfa619SBo Shen SNDRV_PCM_INFO_PAUSE, 6592dfa619SBo Shen .period_bytes_min = 32, 6692dfa619SBo Shen .period_bytes_max = 8192, 6792dfa619SBo Shen .periods_min = 2, 6892dfa619SBo Shen .periods_max = 1024, 6992dfa619SBo Shen .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, 7092dfa619SBo Shen }; 7192dfa619SBo Shen 7292dfa619SBo Shen 7392dfa619SBo Shen /*--------------------------------------------------------------------------*\ 7492dfa619SBo Shen * Data types 7592dfa619SBo Shen \*--------------------------------------------------------------------------*/ 7692dfa619SBo Shen struct atmel_runtime_data { 7792dfa619SBo Shen struct atmel_pcm_dma_params *params; 7892dfa619SBo Shen dma_addr_t dma_buffer; /* physical address of dma buffer */ 7992dfa619SBo Shen dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ 8092dfa619SBo Shen size_t period_size; 8192dfa619SBo Shen 8292dfa619SBo Shen dma_addr_t period_ptr; /* physical address of next period */ 8392dfa619SBo Shen }; 8492dfa619SBo Shen 8592dfa619SBo Shen /*--------------------------------------------------------------------------*\ 8692dfa619SBo Shen * ISR 8792dfa619SBo Shen \*--------------------------------------------------------------------------*/ 8892dfa619SBo Shen static void atmel_pcm_dma_irq(u32 ssc_sr, 8992dfa619SBo Shen struct snd_pcm_substream *substream) 9092dfa619SBo Shen { 9192dfa619SBo Shen struct atmel_runtime_data *prtd = substream->runtime->private_data; 9292dfa619SBo Shen struct atmel_pcm_dma_params *params = prtd->params; 9392dfa619SBo Shen static int count; 9492dfa619SBo Shen 9592dfa619SBo Shen count++; 9692dfa619SBo Shen 9792dfa619SBo Shen if (ssc_sr & params->mask->ssc_endbuf) { 9892dfa619SBo Shen pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", 9992dfa619SBo Shen substream->stream == SNDRV_PCM_STREAM_PLAYBACK 10092dfa619SBo Shen ? "underrun" : "overrun", 10192dfa619SBo Shen params->name, ssc_sr, count); 10292dfa619SBo Shen 10392dfa619SBo Shen /* re-start the PDC */ 10492dfa619SBo Shen ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 10592dfa619SBo Shen params->mask->pdc_disable); 10692dfa619SBo Shen prtd->period_ptr += prtd->period_size; 10792dfa619SBo Shen if (prtd->period_ptr >= prtd->dma_buffer_end) 10892dfa619SBo Shen prtd->period_ptr = prtd->dma_buffer; 10992dfa619SBo Shen 11092dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xpr, 11192dfa619SBo Shen prtd->period_ptr); 11292dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xcr, 11392dfa619SBo Shen prtd->period_size / params->pdc_xfer_size); 11492dfa619SBo Shen ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 11592dfa619SBo Shen params->mask->pdc_enable); 11692dfa619SBo Shen } 11792dfa619SBo Shen 11892dfa619SBo Shen if (ssc_sr & params->mask->ssc_endx) { 11992dfa619SBo Shen /* Load the PDC next pointer and counter registers */ 12092dfa619SBo Shen prtd->period_ptr += prtd->period_size; 12192dfa619SBo Shen if (prtd->period_ptr >= prtd->dma_buffer_end) 12292dfa619SBo Shen prtd->period_ptr = prtd->dma_buffer; 12392dfa619SBo Shen 12492dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xnpr, 12592dfa619SBo Shen prtd->period_ptr); 12692dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xncr, 12792dfa619SBo Shen prtd->period_size / params->pdc_xfer_size); 12892dfa619SBo Shen } 12992dfa619SBo Shen 13092dfa619SBo Shen snd_pcm_period_elapsed(substream); 13192dfa619SBo Shen } 13292dfa619SBo Shen 13392dfa619SBo Shen 13492dfa619SBo Shen /*--------------------------------------------------------------------------*\ 13592dfa619SBo Shen * PCM operations 13692dfa619SBo Shen \*--------------------------------------------------------------------------*/ 137a94e3f2dSKuninori Morimoto static int atmel_pcm_hw_params(struct snd_soc_component *component, 138a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream, 13992dfa619SBo Shen struct snd_pcm_hw_params *params) 14092dfa619SBo Shen { 14192dfa619SBo Shen struct snd_pcm_runtime *runtime = substream->runtime; 14292dfa619SBo Shen struct atmel_runtime_data *prtd = runtime->private_data; 143b1839ebfSKuninori Morimoto struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 14492dfa619SBo Shen 14592dfa619SBo Shen /* this may get called several times by oss emulation 14692dfa619SBo Shen * with different params */ 14792dfa619SBo Shen 148b434d707SKuninori Morimoto prtd->params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); 14992dfa619SBo Shen prtd->params->dma_intr_handler = atmel_pcm_dma_irq; 15092dfa619SBo Shen 15192dfa619SBo Shen prtd->dma_buffer = runtime->dma_addr; 15292dfa619SBo Shen prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; 15392dfa619SBo Shen prtd->period_size = params_period_bytes(params); 15492dfa619SBo Shen 15592dfa619SBo Shen pr_debug("atmel-pcm: " 15692dfa619SBo Shen "hw_params: DMA for %s initialized " 157153f5a18SJoachim Eastwood "(dma_bytes=%zu, period_size=%zu)\n", 15892dfa619SBo Shen prtd->params->name, 15992dfa619SBo Shen runtime->dma_bytes, 16092dfa619SBo Shen prtd->period_size); 16192dfa619SBo Shen return 0; 16292dfa619SBo Shen } 16392dfa619SBo Shen 164a94e3f2dSKuninori Morimoto static int atmel_pcm_hw_free(struct snd_soc_component *component, 165a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream) 16692dfa619SBo Shen { 16792dfa619SBo Shen struct atmel_runtime_data *prtd = substream->runtime->private_data; 16892dfa619SBo Shen struct atmel_pcm_dma_params *params = prtd->params; 16992dfa619SBo Shen 17092dfa619SBo Shen if (params != NULL) { 17192dfa619SBo Shen ssc_writex(params->ssc->regs, SSC_PDC_PTCR, 17292dfa619SBo Shen params->mask->pdc_disable); 17392dfa619SBo Shen prtd->params->dma_intr_handler = NULL; 17492dfa619SBo Shen } 17592dfa619SBo Shen 17692dfa619SBo Shen return 0; 17792dfa619SBo Shen } 17892dfa619SBo Shen 179a94e3f2dSKuninori Morimoto static int atmel_pcm_prepare(struct snd_soc_component *component, 180a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream) 18192dfa619SBo Shen { 18292dfa619SBo Shen struct atmel_runtime_data *prtd = substream->runtime->private_data; 18392dfa619SBo Shen struct atmel_pcm_dma_params *params = prtd->params; 18492dfa619SBo Shen 18592dfa619SBo Shen ssc_writex(params->ssc->regs, SSC_IDR, 18692dfa619SBo Shen params->mask->ssc_endx | params->mask->ssc_endbuf); 18792dfa619SBo Shen ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 18892dfa619SBo Shen params->mask->pdc_disable); 18992dfa619SBo Shen return 0; 19092dfa619SBo Shen } 19192dfa619SBo Shen 192a94e3f2dSKuninori Morimoto static int atmel_pcm_trigger(struct snd_soc_component *component, 193a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream, int cmd) 19492dfa619SBo Shen { 19592dfa619SBo Shen struct snd_pcm_runtime *rtd = substream->runtime; 19692dfa619SBo Shen struct atmel_runtime_data *prtd = rtd->private_data; 19792dfa619SBo Shen struct atmel_pcm_dma_params *params = prtd->params; 19892dfa619SBo Shen int ret = 0; 19992dfa619SBo Shen 20092dfa619SBo Shen pr_debug("atmel-pcm:buffer_size = %ld," 201153f5a18SJoachim Eastwood "dma_area = %p, dma_bytes = %zu\n", 20292dfa619SBo Shen rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); 20392dfa619SBo Shen 20492dfa619SBo Shen switch (cmd) { 20592dfa619SBo Shen case SNDRV_PCM_TRIGGER_START: 20692dfa619SBo Shen prtd->period_ptr = prtd->dma_buffer; 20792dfa619SBo Shen 20892dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xpr, 20992dfa619SBo Shen prtd->period_ptr); 21092dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xcr, 21192dfa619SBo Shen prtd->period_size / params->pdc_xfer_size); 21292dfa619SBo Shen 21392dfa619SBo Shen prtd->period_ptr += prtd->period_size; 21492dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xnpr, 21592dfa619SBo Shen prtd->period_ptr); 21692dfa619SBo Shen ssc_writex(params->ssc->regs, params->pdc->xncr, 21792dfa619SBo Shen prtd->period_size / params->pdc_xfer_size); 21892dfa619SBo Shen 21992dfa619SBo Shen pr_debug("atmel-pcm: trigger: " 22092dfa619SBo Shen "period_ptr=%lx, xpr=%u, " 22192dfa619SBo Shen "xcr=%u, xnpr=%u, xncr=%u\n", 22292dfa619SBo Shen (unsigned long)prtd->period_ptr, 22392dfa619SBo Shen ssc_readx(params->ssc->regs, params->pdc->xpr), 22492dfa619SBo Shen ssc_readx(params->ssc->regs, params->pdc->xcr), 22592dfa619SBo Shen ssc_readx(params->ssc->regs, params->pdc->xnpr), 22692dfa619SBo Shen ssc_readx(params->ssc->regs, params->pdc->xncr)); 22792dfa619SBo Shen 22892dfa619SBo Shen ssc_writex(params->ssc->regs, SSC_IER, 22992dfa619SBo Shen params->mask->ssc_endx | params->mask->ssc_endbuf); 23092dfa619SBo Shen ssc_writex(params->ssc->regs, SSC_PDC_PTCR, 23192dfa619SBo Shen params->mask->pdc_enable); 23292dfa619SBo Shen 23392dfa619SBo Shen pr_debug("sr=%u imr=%u\n", 23492dfa619SBo Shen ssc_readx(params->ssc->regs, SSC_SR), 23592dfa619SBo Shen ssc_readx(params->ssc->regs, SSC_IER)); 23692dfa619SBo Shen break; /* SNDRV_PCM_TRIGGER_START */ 23792dfa619SBo Shen 23892dfa619SBo Shen case SNDRV_PCM_TRIGGER_STOP: 23992dfa619SBo Shen case SNDRV_PCM_TRIGGER_SUSPEND: 24092dfa619SBo Shen case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 24192dfa619SBo Shen ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 24292dfa619SBo Shen params->mask->pdc_disable); 24392dfa619SBo Shen break; 24492dfa619SBo Shen 24592dfa619SBo Shen case SNDRV_PCM_TRIGGER_RESUME: 24692dfa619SBo Shen case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 24792dfa619SBo Shen ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, 24892dfa619SBo Shen params->mask->pdc_enable); 24992dfa619SBo Shen break; 25092dfa619SBo Shen 25192dfa619SBo Shen default: 25292dfa619SBo Shen ret = -EINVAL; 25392dfa619SBo Shen } 25492dfa619SBo Shen 25592dfa619SBo Shen return ret; 25692dfa619SBo Shen } 25792dfa619SBo Shen 258a94e3f2dSKuninori Morimoto static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component, 25992dfa619SBo Shen struct snd_pcm_substream *substream) 26092dfa619SBo Shen { 26192dfa619SBo Shen struct snd_pcm_runtime *runtime = substream->runtime; 26292dfa619SBo Shen struct atmel_runtime_data *prtd = runtime->private_data; 26392dfa619SBo Shen struct atmel_pcm_dma_params *params = prtd->params; 26492dfa619SBo Shen dma_addr_t ptr; 26592dfa619SBo Shen snd_pcm_uframes_t x; 26692dfa619SBo Shen 26792dfa619SBo Shen ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); 26892dfa619SBo Shen x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); 26992dfa619SBo Shen 27092dfa619SBo Shen if (x == runtime->buffer_size) 27192dfa619SBo Shen x = 0; 27292dfa619SBo Shen 27392dfa619SBo Shen return x; 27492dfa619SBo Shen } 27592dfa619SBo Shen 276a94e3f2dSKuninori Morimoto static int atmel_pcm_open(struct snd_soc_component *component, 277a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream) 27892dfa619SBo Shen { 27992dfa619SBo Shen struct snd_pcm_runtime *runtime = substream->runtime; 28092dfa619SBo Shen struct atmel_runtime_data *prtd; 28192dfa619SBo Shen int ret = 0; 28292dfa619SBo Shen 28392dfa619SBo Shen snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware); 28492dfa619SBo Shen 28592dfa619SBo Shen /* ensure that buffer size is a multiple of period size */ 28692dfa619SBo Shen ret = snd_pcm_hw_constraint_integer(runtime, 28792dfa619SBo Shen SNDRV_PCM_HW_PARAM_PERIODS); 28892dfa619SBo Shen if (ret < 0) 28992dfa619SBo Shen goto out; 29092dfa619SBo Shen 29192dfa619SBo Shen prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL); 29292dfa619SBo Shen if (prtd == NULL) { 29392dfa619SBo Shen ret = -ENOMEM; 29492dfa619SBo Shen goto out; 29592dfa619SBo Shen } 29692dfa619SBo Shen runtime->private_data = prtd; 29792dfa619SBo Shen 29892dfa619SBo Shen out: 29992dfa619SBo Shen return ret; 30092dfa619SBo Shen } 30192dfa619SBo Shen 302a94e3f2dSKuninori Morimoto static int atmel_pcm_close(struct snd_soc_component *component, 303a94e3f2dSKuninori Morimoto struct snd_pcm_substream *substream) 30492dfa619SBo Shen { 30592dfa619SBo Shen struct atmel_runtime_data *prtd = substream->runtime->private_data; 30692dfa619SBo Shen 30792dfa619SBo Shen kfree(prtd); 30892dfa619SBo Shen return 0; 30992dfa619SBo Shen } 31092dfa619SBo Shen 311a94e3f2dSKuninori Morimoto static const struct snd_soc_component_driver atmel_soc_platform = { 31292dfa619SBo Shen .open = atmel_pcm_open, 31392dfa619SBo Shen .close = atmel_pcm_close, 31492dfa619SBo Shen .hw_params = atmel_pcm_hw_params, 31592dfa619SBo Shen .hw_free = atmel_pcm_hw_free, 31692dfa619SBo Shen .prepare = atmel_pcm_prepare, 31792dfa619SBo Shen .trigger = atmel_pcm_trigger, 31892dfa619SBo Shen .pointer = atmel_pcm_pointer, 319a94e3f2dSKuninori Morimoto .pcm_construct = atmel_pcm_new, 32092dfa619SBo Shen }; 32192dfa619SBo Shen 32292dfa619SBo Shen int atmel_pcm_pdc_platform_register(struct device *dev) 32392dfa619SBo Shen { 3246dea9df8SKuninori Morimoto return devm_snd_soc_register_component(dev, &atmel_soc_platform, 3256dea9df8SKuninori Morimoto NULL, 0); 32692dfa619SBo Shen } 32792dfa619SBo Shen EXPORT_SYMBOL(atmel_pcm_pdc_platform_register); 32892dfa619SBo Shen 32992dfa619SBo Shen MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); 33092dfa619SBo Shen MODULE_DESCRIPTION("Atmel PCM module"); 33192dfa619SBo Shen MODULE_LICENSE("GPL"); 332