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
atmel_pcm_new(struct snd_soc_component * component,struct snd_soc_pcm_runtime * rtd)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
4722eee4d3SLars-Peter Clausen snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
4822eee4d3SLars-Peter Clausen card->dev, ATMEL_SSC_DMABUF_SIZE,
4922eee4d3SLars-Peter Clausen ATMEL_SSC_DMABUF_SIZE);
50488cb533SAlexandre Belloni
5122eee4d3SLars-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 \*--------------------------------------------------------------------------*/
atmel_pcm_dma_irq(u32 ssc_sr,struct snd_pcm_substream * substream)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 \*--------------------------------------------------------------------------*/
atmel_pcm_hw_params(struct snd_soc_component * component,struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)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;
143*6547effcSKuninori Morimoto struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
14492dfa619SBo Shen
14592dfa619SBo Shen /* this may get called several times by oss emulation
14692dfa619SBo Shen * with different params */
14792dfa619SBo Shen
148*6547effcSKuninori Morimoto prtd->params = snd_soc_dai_get_dma_data(snd_soc_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
atmel_pcm_hw_free(struct snd_soc_component * component,struct snd_pcm_substream * substream)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
atmel_pcm_prepare(struct snd_soc_component * component,struct snd_pcm_substream * substream)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
atmel_pcm_trigger(struct snd_soc_component * component,struct snd_pcm_substream * substream,int cmd)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
atmel_pcm_pointer(struct snd_soc_component * component,struct snd_pcm_substream * substream)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
atmel_pcm_open(struct snd_soc_component * component,struct snd_pcm_substream * substream)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
atmel_pcm_close(struct snd_soc_component * component,struct snd_pcm_substream * substream)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
atmel_pcm_pdc_platform_register(struct device * dev)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