[alsa-devel] [PATCH 03/17] ASoC: Add a generic dmaengine_pcm driver
Markus Pargmann
mpa at pengutronix.de
Tue Apr 16 16:11:05 CEST 2013
On Mon, Apr 15, 2013 at 07:19:50PM +0200, Lars-Peter Clausen wrote:
> This patch adds a generic dmaengine PCM driver. It builds on top of the
> dmaengine PCM library and adds the missing pieces like DMA channel management,
> buffer management and channel configuration. It will be able to replace the
> majority of the existing platform specific dmaengine based PCM drivers.
> Devicetree is used to map the DMA channels to the PCM device.
>
> Signed-off-by: Lars-Peter Clausen <lars at metafoo.de>
> ---
> include/sound/dmaengine_pcm.h | 25 ++++
> sound/soc/Kconfig | 4 +
> sound/soc/Makefile | 4 +
> sound/soc/soc-generic-dmaengine-pcm.c | 241 ++++++++++++++++++++++++++++++++++
> 4 files changed, 274 insertions(+)
> create mode 100644 sound/soc/soc-generic-dmaengine-pcm.c
>
> diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h
> index d015d67..e0bf24e 100644
> --- a/include/sound/dmaengine_pcm.h
> +++ b/include/sound/dmaengine_pcm.h
> @@ -72,4 +72,29 @@ void snd_dmaengine_pcm_set_config_from_dai_data(
> const struct snd_dmaengine_dai_dma_data *dma_data,
> struct dma_slave_config *config);
>
> +/**
> + * struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM
> + * @prepare_slave_config: Callback used to fill in the DMA slave_config for a
> + * PCM substream. Will be called from the PCM drivers hwparams callback.
> + * @pcm_hardware: snd_pcm_hardware struct to be used for the PCM.
> + * @prealloc_buffer_size: Size of the preallocated audio buffer.
> + */
> +struct snd_dmaengine_pcm_config {
> + int (*prepare_slave_config)(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct dma_slave_config *slave_config);
> +
> + const struct snd_pcm_hardware *pcm_hardware;
> + unsigned int prealloc_buffer_size;
> +};
> +
> +int snd_dmaengine_pcm_register(struct device *dev,
> + const struct snd_dmaengine_pcm_config *config,
> + unsigned int flags);
> +void snd_dmaengine_pcm_unregister(struct device *dev);
> +
> +int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct dma_slave_config *slave_config);
> +
> #endif
> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
> index 5da8ca7..9e675c7 100644
> --- a/sound/soc/Kconfig
> +++ b/sound/soc/Kconfig
> @@ -29,6 +29,10 @@ config SND_SOC_AC97_BUS
> config SND_SOC_DMAENGINE_PCM
> bool
>
> +config SND_SOC_GENERIC_DMAENGINE_PCM
> + bool
> + select SND_SOC_DMAENGINE_PCM
> +
> # All the supported SoCs
> source "sound/soc/atmel/Kconfig"
> source "sound/soc/au1x/Kconfig"
> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
> index 99f32f7..197b6ae 100644
> --- a/sound/soc/Makefile
> +++ b/sound/soc/Makefile
> @@ -5,6 +5,10 @@ ifneq ($(CONFIG_SND_SOC_DMAENGINE_PCM),)
> snd-soc-core-objs += soc-dmaengine-pcm.o
> endif
>
> +ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
> +snd-soc-core-objs += soc-generic-dmaengine-pcm.o
> +endif
> +
> obj-$(CONFIG_SND_SOC) += snd-soc-core.o
> obj-$(CONFIG_SND_SOC) += codecs/
> obj-$(CONFIG_SND_SOC) += generic/
> diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c
> new file mode 100644
> index 0000000..acfc926
> --- /dev/null
> +++ b/sound/soc/soc-generic-dmaengine-pcm.c
> @@ -0,0 +1,241 @@
> +/*
> + * Copyright (C) 2013, Analog Devices Inc.
> + * Author: Lars-Peter Clausen <lars at metafoo.de>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 675 Mass Ave, Cambridge, MA 02139, USA.
> + *
> + */
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/dmaengine.h>
> +#include <linux/slab.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +
> +#include <sound/dmaengine_pcm.h>
> +
> +struct dmaengine_pcm {
> + struct dma_chan *chan[SNDRV_PCM_STREAM_CAPTURE + 1];
> + const struct snd_dmaengine_pcm_config *config;
> + struct snd_soc_platform platform;
> +};
> +
> +static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p)
> +{
> + return container_of(p, struct dmaengine_pcm, platform);
> +}
> +
> +/**
> + * snd_dmaengine_pcm_prepare_slave_config() - Generic prepare_slave_config callback
> + * @substream: PCM substream
> + * @params: hw_params
> + * @slave_config: DMA slave config to prepare
> + *
> + * This function can be used as a generic prepare_slave_config callback for
> + * platforms which make use of the snd_dmaengine_dai_dma_data struct for their
> + * DAI DMA data. Internally the function will first call
> + * snd_hwparams_to_dma_slave_config to fill in the slave config based on the
> + * hw_params, followed by snd_dmaengine_set_config_from_dai_data to fill in the
> + * remaining fields based on the DAI DMA data.
> + */
> +int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
> +{
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct snd_dmaengine_dai_dma_data *dma_data;
> + int ret;
> +
> + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
> +
> + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
> + if (ret)
> + return ret;
> +
> + snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data,
> + slave_config);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_prepare_slave_config);
> +
> +static int dmaengine_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
> + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
> + struct dma_slave_config slave_config;
> + int ret;
> +
> + if (pcm->config->prepare_slave_config) {
> + ret = pcm->config->prepare_slave_config(substream, params,
> + &slave_config);
> + if (ret)
> + return ret;
> +
> + ret = dmaengine_slave_config(chan, &slave_config);
> + if (ret)
> + return ret;
> + }
> +
> + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
> +}
> +
> +static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
> +{
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
> + struct dma_chan *chan = pcm->chan[substream->stream];
> + int ret;
> +
> + ret = snd_soc_set_runtime_hwparams(substream,
> + pcm->config->pcm_hardware);
> + if (ret)
> + return ret;
> +
> + return snd_dmaengine_pcm_open(substream, chan);
> +}
> +
> +static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm,
> + struct snd_pcm_substream *substream)
> +{
> + if (!pcm->chan[substream->stream])
> + return NULL;
> +
> + return pcm->chan[substream->stream]->device->dev;
> +}
> +
> +static void dmaengine_pcm_free(struct snd_pcm *pcm)
> +{
> + snd_pcm_lib_preallocate_free_for_all(pcm);
> +}
> +
> +static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
> +{
> + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
> + const struct snd_dmaengine_pcm_config *config = pcm->config;
> + struct snd_pcm_substream *substream;
> + unsigned int i;
> + int ret;
> +
> + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
> + substream = rtd->pcm->streams[i].substream;
> + if (!substream)
> + continue;
> +
> + if (!pcm->chan[i]) {
> + dev_err(rtd->platform->dev,
> + "Missing dma channel for stream: %d\n", i);
> + ret = -EINVAL;
> + goto err_free;
> + }
> +
> + ret = snd_pcm_lib_preallocate_pages(substream,
> + SNDRV_DMA_TYPE_DEV,
> + dmaengine_dma_dev(pcm, substream),
> + config->prealloc_buffer_size,
> + config->pcm_hardware->buffer_bytes_max);
> + if (ret)
> + goto err_free;
> + }
> +
> + return 0;
> +
> +err_free:
> + dmaengine_pcm_free(rtd->pcm);
> + return ret;
> +}
> +
> +static const struct snd_pcm_ops dmaengine_pcm_ops = {
> + .open = dmaengine_pcm_open,
> + .close = snd_dmaengine_pcm_close,
> + .ioctl = snd_pcm_lib_ioctl,
> + .hw_params = dmaengine_pcm_hw_params,
> + .hw_free = snd_pcm_lib_free_pages,
> + .trigger = snd_dmaengine_pcm_trigger,
> + .pointer = snd_dmaengine_pcm_pointer,
> +};
> +
> +static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
> + .ops = &dmaengine_pcm_ops,
> + .pcm_new = dmaengine_pcm_new,
> + .pcm_free = dmaengine_pcm_free,
> +};
> +
> +static const char * const dmaengine_pcm_dma_channel_names[] = {
> + [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
> + [SNDRV_PCM_STREAM_CAPTURE] = "rx",
> +};
> +
> +/**
> + * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
> + * @dev: The parent device for the PCM device
> + * @config: Platform specific PCM configuration
> + * @flags: Platform specific quirks
> + */
> +int snd_dmaengine_pcm_register(struct device *dev,
> + const struct snd_dmaengine_pcm_config *config, unsigned int flags)
> +{
> + struct dmaengine_pcm *pcm;
> + unsigned int i;
> +
> + if (!dev->of_node)
> + return -EINVAL;
> +
> + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
> + if (!pcm)
> + return -ENOMEM;
> +
> + pcm->config = config;
> +
> + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
> + pcm->chan[i] = of_dma_request_slave_channel(dev->of_node,
> + dmaengine_pcm_dma_channel_names[i]);
> + }
Perhaps you should use dma_request_slave_channel here? Then you could
also drop the of_dma.h include.
Regards,
Markus
> +
> + return snd_soc_add_platform(dev, &pcm->platform,
> + &dmaengine_pcm_platform);
> +}
> +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);
> +
> +/**
> + * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
> + * @dev: Parent device the PCM was register with
> + *
> + * Removes a dmaengine based PCM device previously registered with
> + * snd_dmaengine_pcm_register.
> + */
> +void snd_dmaengine_pcm_unregister(struct device *dev)
> +{
> + struct snd_soc_platform *platform;
> + struct dmaengine_pcm *pcm;
> + unsigned int i;
> +
> + platform = snd_soc_lookup_platform(dev);
> + if (!platform)
> + return;
> +
> + pcm = soc_platform_to_pcm(platform);
> +
> + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
> + if (pcm->chan[i])
> + dma_release_channel(pcm->chan[i]);
> + }
> +
> + snd_soc_remove_platform(platform);
> + kfree(pcm);
> +}
> +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister);
> +
> +MODULE_LICENSE("GPL");
> --
> 1.8.0
>
>
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the Alsa-devel
mailing list