xref: /linux/sound/soc/atmel/atmel-pcm-pdc.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
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