18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * linux/sound/soc/pxa/mmp-pcm.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Marvell International Ltd.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/module.h>
88c2ecf20Sopenharmony_ci#include <linux/init.h>
98c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
128c2ecf20Sopenharmony_ci#include <linux/dmaengine.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_data/dma-mmp_tdma.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_data/mmp_audio.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <sound/pxa2xx-lib.h>
178c2ecf20Sopenharmony_ci#include <sound/core.h>
188c2ecf20Sopenharmony_ci#include <sound/pcm.h>
198c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
208c2ecf20Sopenharmony_ci#include <sound/soc.h>
218c2ecf20Sopenharmony_ci#include <sound/dmaengine_pcm.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define DRV_NAME "mmp-pcm"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct mmp_dma_data {
268c2ecf20Sopenharmony_ci	int ssp_id;
278c2ecf20Sopenharmony_ci	struct resource *dma_res;
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP |	\
318c2ecf20Sopenharmony_ci		SNDRV_PCM_INFO_MMAP_VALID |	\
328c2ecf20Sopenharmony_ci		SNDRV_PCM_INFO_INTERLEAVED |	\
338c2ecf20Sopenharmony_ci		SNDRV_PCM_INFO_PAUSE |		\
348c2ecf20Sopenharmony_ci		SNDRV_PCM_INFO_RESUME |		\
358c2ecf20Sopenharmony_ci		SNDRV_PCM_INFO_NO_PERIOD_WAKEUP)
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic struct snd_pcm_hardware mmp_pcm_hardware[] = {
388c2ecf20Sopenharmony_ci	{
398c2ecf20Sopenharmony_ci		.info			= MMP_PCM_INFO,
408c2ecf20Sopenharmony_ci		.period_bytes_min	= 1024,
418c2ecf20Sopenharmony_ci		.period_bytes_max	= 2048,
428c2ecf20Sopenharmony_ci		.periods_min		= 2,
438c2ecf20Sopenharmony_ci		.periods_max		= 32,
448c2ecf20Sopenharmony_ci		.buffer_bytes_max	= 4096,
458c2ecf20Sopenharmony_ci		.fifo_size		= 32,
468c2ecf20Sopenharmony_ci	},
478c2ecf20Sopenharmony_ci	{
488c2ecf20Sopenharmony_ci		.info			= MMP_PCM_INFO,
498c2ecf20Sopenharmony_ci		.period_bytes_min	= 1024,
508c2ecf20Sopenharmony_ci		.period_bytes_max	= 2048,
518c2ecf20Sopenharmony_ci		.periods_min		= 2,
528c2ecf20Sopenharmony_ci		.periods_max		= 32,
538c2ecf20Sopenharmony_ci		.buffer_bytes_max	= 4096,
548c2ecf20Sopenharmony_ci		.fifo_size		= 32,
558c2ecf20Sopenharmony_ci	},
568c2ecf20Sopenharmony_ci};
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int mmp_pcm_hw_params(struct snd_soc_component *component,
598c2ecf20Sopenharmony_ci			     struct snd_pcm_substream *substream,
608c2ecf20Sopenharmony_ci			     struct snd_pcm_hw_params *params)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
638c2ecf20Sopenharmony_ci	struct dma_slave_config slave_config;
648c2ecf20Sopenharmony_ci	int ret;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	ret =
678c2ecf20Sopenharmony_ci	    snd_dmaengine_pcm_prepare_slave_config(substream, params,
688c2ecf20Sopenharmony_ci						   &slave_config);
698c2ecf20Sopenharmony_ci	if (ret)
708c2ecf20Sopenharmony_ci		return ret;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	ret = dmaengine_slave_config(chan, &slave_config);
738c2ecf20Sopenharmony_ci	if (ret)
748c2ecf20Sopenharmony_ci		return ret;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	return 0;
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic int mmp_pcm_trigger(struct snd_soc_component *component,
828c2ecf20Sopenharmony_ci			   struct snd_pcm_substream *substream, int cmd)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	return snd_dmaengine_pcm_trigger(substream, cmd);
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic snd_pcm_uframes_t mmp_pcm_pointer(struct snd_soc_component *component,
888c2ecf20Sopenharmony_ci					 struct snd_pcm_substream *substream)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	return snd_dmaengine_pcm_pointer(substream);
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic bool filter(struct dma_chan *chan, void *param)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct mmp_dma_data *dma_data = param;
968c2ecf20Sopenharmony_ci	bool found = false;
978c2ecf20Sopenharmony_ci	char *devname;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
1008c2ecf20Sopenharmony_ci		dma_data->ssp_id);
1018c2ecf20Sopenharmony_ci	if (devname && (strcmp(dev_name(chan->device->dev), devname) == 0) &&
1028c2ecf20Sopenharmony_ci		(chan->chan_id == dma_data->dma_res->start)) {
1038c2ecf20Sopenharmony_ci		found = true;
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	kfree(devname);
1078c2ecf20Sopenharmony_ci	return found;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int mmp_pcm_open(struct snd_soc_component *component,
1118c2ecf20Sopenharmony_ci			struct snd_pcm_substream *substream)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
1148c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(component->dev);
1158c2ecf20Sopenharmony_ci	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
1168c2ecf20Sopenharmony_ci	struct mmp_dma_data dma_data;
1178c2ecf20Sopenharmony_ci	struct resource *r;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream);
1208c2ecf20Sopenharmony_ci	if (!r)
1218c2ecf20Sopenharmony_ci		return -EBUSY;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	snd_soc_set_runtime_hwparams(substream,
1248c2ecf20Sopenharmony_ci				&mmp_pcm_hardware[substream->stream]);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	dma_data.dma_res = r;
1278c2ecf20Sopenharmony_ci	dma_data.ssp_id = cpu_dai->id;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return snd_dmaengine_pcm_open_request_chan(substream, filter,
1308c2ecf20Sopenharmony_ci		    &dma_data);
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic int mmp_pcm_close(struct snd_soc_component *component,
1348c2ecf20Sopenharmony_ci			 struct snd_pcm_substream *substream)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	return snd_dmaengine_pcm_close_release_chan(substream);
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic int mmp_pcm_mmap(struct snd_soc_component *component,
1408c2ecf20Sopenharmony_ci			struct snd_pcm_substream *substream,
1418c2ecf20Sopenharmony_ci			struct vm_area_struct *vma)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
1448c2ecf20Sopenharmony_ci	unsigned long off = vma->vm_pgoff;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
1478c2ecf20Sopenharmony_ci	return remap_pfn_range(vma, vma->vm_start,
1488c2ecf20Sopenharmony_ci		__phys_to_pfn(runtime->dma_addr) + off,
1498c2ecf20Sopenharmony_ci		vma->vm_end - vma->vm_start, vma->vm_page_prot);
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic void mmp_pcm_free_dma_buffers(struct snd_soc_component *component,
1538c2ecf20Sopenharmony_ci				     struct snd_pcm *pcm)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream;
1568c2ecf20Sopenharmony_ci	struct snd_dma_buffer *buf;
1578c2ecf20Sopenharmony_ci	int stream;
1588c2ecf20Sopenharmony_ci	struct gen_pool *gpool;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	gpool = sram_get_gpool("asram");
1618c2ecf20Sopenharmony_ci	if (!gpool)
1628c2ecf20Sopenharmony_ci		return;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	for (stream = 0; stream < 2; stream++) {
1658c2ecf20Sopenharmony_ci		size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci		substream = pcm->streams[stream].substream;
1688c2ecf20Sopenharmony_ci		if (!substream)
1698c2ecf20Sopenharmony_ci			continue;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		buf = &substream->dma_buffer;
1728c2ecf20Sopenharmony_ci		if (!buf->area)
1738c2ecf20Sopenharmony_ci			continue;
1748c2ecf20Sopenharmony_ci		gen_pool_free(gpool, (unsigned long)buf->area, size);
1758c2ecf20Sopenharmony_ci		buf->area = NULL;
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
1818c2ecf20Sopenharmony_ci								int stream)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	struct snd_dma_buffer *buf = &substream->dma_buffer;
1848c2ecf20Sopenharmony_ci	size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
1858c2ecf20Sopenharmony_ci	struct gen_pool *gpool;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	buf->dev.type = SNDRV_DMA_TYPE_DEV;
1888c2ecf20Sopenharmony_ci	buf->dev.dev = substream->pcm->card->dev;
1898c2ecf20Sopenharmony_ci	buf->private_data = NULL;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	gpool = sram_get_gpool("asram");
1928c2ecf20Sopenharmony_ci	if (!gpool)
1938c2ecf20Sopenharmony_ci		return -ENOMEM;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	buf->area = gen_pool_dma_alloc(gpool, size, &buf->addr);
1968c2ecf20Sopenharmony_ci	if (!buf->area)
1978c2ecf20Sopenharmony_ci		return -ENOMEM;
1988c2ecf20Sopenharmony_ci	buf->bytes = size;
1998c2ecf20Sopenharmony_ci	return 0;
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic int mmp_pcm_new(struct snd_soc_component *component,
2038c2ecf20Sopenharmony_ci		       struct snd_soc_pcm_runtime *rtd)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci	struct snd_pcm_substream *substream;
2068c2ecf20Sopenharmony_ci	struct snd_pcm *pcm = rtd->pcm;
2078c2ecf20Sopenharmony_ci	int ret = 0, stream;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	for (stream = 0; stream < 2; stream++) {
2108c2ecf20Sopenharmony_ci		substream = pcm->streams[stream].substream;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		ret = mmp_pcm_preallocate_dma_buffer(substream,	stream);
2138c2ecf20Sopenharmony_ci		if (ret)
2148c2ecf20Sopenharmony_ci			goto err;
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return 0;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cierr:
2208c2ecf20Sopenharmony_ci	mmp_pcm_free_dma_buffers(component, pcm);
2218c2ecf20Sopenharmony_ci	return ret;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver mmp_soc_component = {
2258c2ecf20Sopenharmony_ci	.name		= DRV_NAME,
2268c2ecf20Sopenharmony_ci	.open		= mmp_pcm_open,
2278c2ecf20Sopenharmony_ci	.close		= mmp_pcm_close,
2288c2ecf20Sopenharmony_ci	.hw_params	= mmp_pcm_hw_params,
2298c2ecf20Sopenharmony_ci	.trigger	= mmp_pcm_trigger,
2308c2ecf20Sopenharmony_ci	.pointer	= mmp_pcm_pointer,
2318c2ecf20Sopenharmony_ci	.mmap		= mmp_pcm_mmap,
2328c2ecf20Sopenharmony_ci	.pcm_construct	= mmp_pcm_new,
2338c2ecf20Sopenharmony_ci	.pcm_destruct	= mmp_pcm_free_dma_buffers,
2348c2ecf20Sopenharmony_ci};
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic int mmp_pcm_probe(struct platform_device *pdev)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	struct mmp_audio_platdata *pdata = pdev->dev.platform_data;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	if (pdata) {
2418c2ecf20Sopenharmony_ci		mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max =
2428c2ecf20Sopenharmony_ci						pdata->buffer_max_playback;
2438c2ecf20Sopenharmony_ci		mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max =
2448c2ecf20Sopenharmony_ci						pdata->period_max_playback;
2458c2ecf20Sopenharmony_ci		mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max =
2468c2ecf20Sopenharmony_ci						pdata->buffer_max_capture;
2478c2ecf20Sopenharmony_ci		mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max =
2488c2ecf20Sopenharmony_ci						pdata->period_max_capture;
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci	return devm_snd_soc_register_component(&pdev->dev, &mmp_soc_component,
2518c2ecf20Sopenharmony_ci					       NULL, 0);
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic struct platform_driver mmp_pcm_driver = {
2558c2ecf20Sopenharmony_ci	.driver = {
2568c2ecf20Sopenharmony_ci		.name = "mmp-pcm-audio",
2578c2ecf20Sopenharmony_ci	},
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	.probe = mmp_pcm_probe,
2608c2ecf20Sopenharmony_ci};
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_cimodule_platform_driver(mmp_pcm_driver);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ciMODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
2658c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MMP Soc Audio DMA module");
2668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2678c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:mmp-pcm-audio");
268