162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * atmel-pcm.c  --  ALSA PCM interface for the Atmel atmel SoC.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2005 SAN People
662306a36Sopenharmony_ci *  Copyright (C) 2008 Atmel
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Based on at91-pcm. by:
1162306a36Sopenharmony_ci * Frank Mandarino <fmandarino@endrelia.com>
1262306a36Sopenharmony_ci * Copyright 2006 Endrelia Technologies Inc.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * Based on pxa2xx-pcm.c by:
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Author:	Nicolas Pitre
1762306a36Sopenharmony_ci * Created:	Nov 30, 2004
1862306a36Sopenharmony_ci * Copyright:	(C) 2004 MontaVista Software, Inc.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/init.h>
2362306a36Sopenharmony_ci#include <linux/platform_device.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/dma-mapping.h>
2662306a36Sopenharmony_ci#include <linux/atmel_pdc.h>
2762306a36Sopenharmony_ci#include <linux/atmel-ssc.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#include <sound/core.h>
3062306a36Sopenharmony_ci#include <sound/pcm.h>
3162306a36Sopenharmony_ci#include <sound/pcm_params.h>
3262306a36Sopenharmony_ci#include <sound/soc.h>
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#include "atmel-pcm.h"
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int atmel_pcm_new(struct snd_soc_component *component,
3862306a36Sopenharmony_ci			 struct snd_soc_pcm_runtime *rtd)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct snd_card *card = rtd->card->snd_card;
4162306a36Sopenharmony_ci	int ret;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
4462306a36Sopenharmony_ci	if (ret)
4562306a36Sopenharmony_ci		return ret;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
4862306a36Sopenharmony_ci				       card->dev, ATMEL_SSC_DMABUF_SIZE,
4962306a36Sopenharmony_ci				       ATMEL_SSC_DMABUF_SIZE);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	return 0;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/*--------------------------------------------------------------------------*\
5562306a36Sopenharmony_ci * Hardware definition
5662306a36Sopenharmony_ci\*--------------------------------------------------------------------------*/
5762306a36Sopenharmony_ci/* TODO: These values were taken from the AT91 platform driver, check
5862306a36Sopenharmony_ci *	 them against real values for AT32
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistatic const struct snd_pcm_hardware atmel_pcm_hardware = {
6162306a36Sopenharmony_ci	.info			= SNDRV_PCM_INFO_MMAP |
6262306a36Sopenharmony_ci				  SNDRV_PCM_INFO_MMAP_VALID |
6362306a36Sopenharmony_ci				  SNDRV_PCM_INFO_INTERLEAVED |
6462306a36Sopenharmony_ci				  SNDRV_PCM_INFO_PAUSE,
6562306a36Sopenharmony_ci	.period_bytes_min	= 32,
6662306a36Sopenharmony_ci	.period_bytes_max	= 8192,
6762306a36Sopenharmony_ci	.periods_min		= 2,
6862306a36Sopenharmony_ci	.periods_max		= 1024,
6962306a36Sopenharmony_ci	.buffer_bytes_max	= ATMEL_SSC_DMABUF_SIZE,
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/*--------------------------------------------------------------------------*\
7462306a36Sopenharmony_ci * Data types
7562306a36Sopenharmony_ci\*--------------------------------------------------------------------------*/
7662306a36Sopenharmony_cistruct atmel_runtime_data {
7762306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params;
7862306a36Sopenharmony_ci	dma_addr_t dma_buffer;		/* physical address of dma buffer */
7962306a36Sopenharmony_ci	dma_addr_t dma_buffer_end;	/* first address beyond DMA buffer */
8062306a36Sopenharmony_ci	size_t period_size;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	dma_addr_t period_ptr;		/* physical address of next period */
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci/*--------------------------------------------------------------------------*\
8662306a36Sopenharmony_ci * ISR
8762306a36Sopenharmony_ci\*--------------------------------------------------------------------------*/
8862306a36Sopenharmony_cistatic void atmel_pcm_dma_irq(u32 ssc_sr,
8962306a36Sopenharmony_ci	struct snd_pcm_substream *substream)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = substream->runtime->private_data;
9262306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params = prtd->params;
9362306a36Sopenharmony_ci	static int count;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	count++;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (ssc_sr & params->mask->ssc_endbuf) {
9862306a36Sopenharmony_ci		pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
9962306a36Sopenharmony_ci				substream->stream == SNDRV_PCM_STREAM_PLAYBACK
10062306a36Sopenharmony_ci				? "underrun" : "overrun",
10162306a36Sopenharmony_ci				params->name, ssc_sr, count);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		/* re-start the PDC */
10462306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
10562306a36Sopenharmony_ci			   params->mask->pdc_disable);
10662306a36Sopenharmony_ci		prtd->period_ptr += prtd->period_size;
10762306a36Sopenharmony_ci		if (prtd->period_ptr >= prtd->dma_buffer_end)
10862306a36Sopenharmony_ci			prtd->period_ptr = prtd->dma_buffer;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xpr,
11162306a36Sopenharmony_ci			   prtd->period_ptr);
11262306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xcr,
11362306a36Sopenharmony_ci			   prtd->period_size / params->pdc_xfer_size);
11462306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
11562306a36Sopenharmony_ci			   params->mask->pdc_enable);
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (ssc_sr & params->mask->ssc_endx) {
11962306a36Sopenharmony_ci		/* Load the PDC next pointer and counter registers */
12062306a36Sopenharmony_ci		prtd->period_ptr += prtd->period_size;
12162306a36Sopenharmony_ci		if (prtd->period_ptr >= prtd->dma_buffer_end)
12262306a36Sopenharmony_ci			prtd->period_ptr = prtd->dma_buffer;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xnpr,
12562306a36Sopenharmony_ci			   prtd->period_ptr);
12662306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xncr,
12762306a36Sopenharmony_ci			   prtd->period_size / params->pdc_xfer_size);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	snd_pcm_period_elapsed(substream);
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci/*--------------------------------------------------------------------------*\
13562306a36Sopenharmony_ci * PCM operations
13662306a36Sopenharmony_ci\*--------------------------------------------------------------------------*/
13762306a36Sopenharmony_cistatic int atmel_pcm_hw_params(struct snd_soc_component *component,
13862306a36Sopenharmony_ci			       struct snd_pcm_substream *substream,
13962306a36Sopenharmony_ci			       struct snd_pcm_hw_params *params)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
14262306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = runtime->private_data;
14362306a36Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* this may get called several times by oss emulation
14662306a36Sopenharmony_ci	 * with different params */
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	prtd->params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
14962306a36Sopenharmony_ci	prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	prtd->dma_buffer = runtime->dma_addr;
15262306a36Sopenharmony_ci	prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
15362306a36Sopenharmony_ci	prtd->period_size = params_period_bytes(params);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	pr_debug("atmel-pcm: "
15662306a36Sopenharmony_ci		"hw_params: DMA for %s initialized "
15762306a36Sopenharmony_ci		"(dma_bytes=%zu, period_size=%zu)\n",
15862306a36Sopenharmony_ci		prtd->params->name,
15962306a36Sopenharmony_ci		runtime->dma_bytes,
16062306a36Sopenharmony_ci		prtd->period_size);
16162306a36Sopenharmony_ci	return 0;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int atmel_pcm_hw_free(struct snd_soc_component *component,
16562306a36Sopenharmony_ci			     struct snd_pcm_substream *substream)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = substream->runtime->private_data;
16862306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params = prtd->params;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (params != NULL) {
17162306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
17262306a36Sopenharmony_ci			   params->mask->pdc_disable);
17362306a36Sopenharmony_ci		prtd->params->dma_intr_handler = NULL;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int atmel_pcm_prepare(struct snd_soc_component *component,
18062306a36Sopenharmony_ci			     struct snd_pcm_substream *substream)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = substream->runtime->private_data;
18362306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params = prtd->params;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	ssc_writex(params->ssc->regs, SSC_IDR,
18662306a36Sopenharmony_ci		   params->mask->ssc_endx | params->mask->ssc_endbuf);
18762306a36Sopenharmony_ci	ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
18862306a36Sopenharmony_ci		   params->mask->pdc_disable);
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int atmel_pcm_trigger(struct snd_soc_component *component,
19362306a36Sopenharmony_ci			     struct snd_pcm_substream *substream, int cmd)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct snd_pcm_runtime *rtd = substream->runtime;
19662306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = rtd->private_data;
19762306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params = prtd->params;
19862306a36Sopenharmony_ci	int ret = 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	pr_debug("atmel-pcm:buffer_size = %ld,"
20162306a36Sopenharmony_ci		"dma_area = %p, dma_bytes = %zu\n",
20262306a36Sopenharmony_ci		rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	switch (cmd) {
20562306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
20662306a36Sopenharmony_ci		prtd->period_ptr = prtd->dma_buffer;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xpr,
20962306a36Sopenharmony_ci			   prtd->period_ptr);
21062306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xcr,
21162306a36Sopenharmony_ci			   prtd->period_size / params->pdc_xfer_size);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		prtd->period_ptr += prtd->period_size;
21462306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xnpr,
21562306a36Sopenharmony_ci			   prtd->period_ptr);
21662306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, params->pdc->xncr,
21762306a36Sopenharmony_ci			   prtd->period_size / params->pdc_xfer_size);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		pr_debug("atmel-pcm: trigger: "
22062306a36Sopenharmony_ci			"period_ptr=%lx, xpr=%u, "
22162306a36Sopenharmony_ci			"xcr=%u, xnpr=%u, xncr=%u\n",
22262306a36Sopenharmony_ci			(unsigned long)prtd->period_ptr,
22362306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, params->pdc->xpr),
22462306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, params->pdc->xcr),
22562306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, params->pdc->xnpr),
22662306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, params->pdc->xncr));
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, SSC_IER,
22962306a36Sopenharmony_ci			   params->mask->ssc_endx | params->mask->ssc_endbuf);
23062306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
23162306a36Sopenharmony_ci			   params->mask->pdc_enable);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		pr_debug("sr=%u imr=%u\n",
23462306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, SSC_SR),
23562306a36Sopenharmony_ci			ssc_readx(params->ssc->regs, SSC_IER));
23662306a36Sopenharmony_ci		break;		/* SNDRV_PCM_TRIGGER_START */
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
23962306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
24062306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
24162306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
24262306a36Sopenharmony_ci			   params->mask->pdc_disable);
24362306a36Sopenharmony_ci		break;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
24662306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
24762306a36Sopenharmony_ci		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
24862306a36Sopenharmony_ci			   params->mask->pdc_enable);
24962306a36Sopenharmony_ci		break;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	default:
25262306a36Sopenharmony_ci		ret = -EINVAL;
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	return ret;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component,
25962306a36Sopenharmony_ci					   struct snd_pcm_substream *substream)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
26262306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = runtime->private_data;
26362306a36Sopenharmony_ci	struct atmel_pcm_dma_params *params = prtd->params;
26462306a36Sopenharmony_ci	dma_addr_t ptr;
26562306a36Sopenharmony_ci	snd_pcm_uframes_t x;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
26862306a36Sopenharmony_ci	x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (x == runtime->buffer_size)
27162306a36Sopenharmony_ci		x = 0;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return x;
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic int atmel_pcm_open(struct snd_soc_component *component,
27762306a36Sopenharmony_ci			  struct snd_pcm_substream *substream)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
28062306a36Sopenharmony_ci	struct atmel_runtime_data *prtd;
28162306a36Sopenharmony_ci	int ret = 0;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	/* ensure that buffer size is a multiple of period size */
28662306a36Sopenharmony_ci	ret = snd_pcm_hw_constraint_integer(runtime,
28762306a36Sopenharmony_ci						SNDRV_PCM_HW_PARAM_PERIODS);
28862306a36Sopenharmony_ci	if (ret < 0)
28962306a36Sopenharmony_ci		goto out;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
29262306a36Sopenharmony_ci	if (prtd == NULL) {
29362306a36Sopenharmony_ci		ret = -ENOMEM;
29462306a36Sopenharmony_ci		goto out;
29562306a36Sopenharmony_ci	}
29662306a36Sopenharmony_ci	runtime->private_data = prtd;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci out:
29962306a36Sopenharmony_ci	return ret;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic int atmel_pcm_close(struct snd_soc_component *component,
30362306a36Sopenharmony_ci			   struct snd_pcm_substream *substream)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	struct atmel_runtime_data *prtd = substream->runtime->private_data;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	kfree(prtd);
30862306a36Sopenharmony_ci	return 0;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic const struct snd_soc_component_driver atmel_soc_platform = {
31262306a36Sopenharmony_ci	.open		= atmel_pcm_open,
31362306a36Sopenharmony_ci	.close		= atmel_pcm_close,
31462306a36Sopenharmony_ci	.hw_params	= atmel_pcm_hw_params,
31562306a36Sopenharmony_ci	.hw_free	= atmel_pcm_hw_free,
31662306a36Sopenharmony_ci	.prepare	= atmel_pcm_prepare,
31762306a36Sopenharmony_ci	.trigger	= atmel_pcm_trigger,
31862306a36Sopenharmony_ci	.pointer	= atmel_pcm_pointer,
31962306a36Sopenharmony_ci	.pcm_construct	= atmel_pcm_new,
32062306a36Sopenharmony_ci};
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ciint atmel_pcm_pdc_platform_register(struct device *dev)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	return devm_snd_soc_register_component(dev, &atmel_soc_platform,
32562306a36Sopenharmony_ci					       NULL, 0);
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ciEXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ciMODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
33062306a36Sopenharmony_ciMODULE_DESCRIPTION("Atmel PCM module");
33162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
332