From 7c1c1d4a7b4ca1266057a3632d27450f5575caf9 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:19:48 +0200 Subject: ASoC: dmaengine-pcm: Make requesting the DMA channel at PCM open optional Refactor the dmaengine PCM library to allow the DMA channel to be requested before opening a PCM substream. snd_dmaengine_pcm_open() now expects a DMA channel instead of a filter function and filter parameter as its parameters. snd_dmaengine_pcm_close() is updated to not release the DMA channel. This allows a dmaengine based PCM driver to request its channels before the substream is opened. The patch also introduces two new functions, snd_dmaengine_pcm_open_request_chan() and snd_dmaengine_pcm_close_release_chan(), which have the same signature and behaviour of the old snd_dmaengine_pcm_{open,close}() and internally use the new variants of these functions. All users of snd_dmaengine_pcm_{open,close}() are updated to use snd_dmaengine_pcm_open_request_chan() and snd_dmaengine_pcm_close_release_chan(). Signed-off-by: Lars-Peter Clausen Tested-by: Stephen Warren Tested-by: Shawn Guo Signed-off-by: Mark Brown --- include/sound/dmaengine_pcm.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index 95620428a59b..d015d67e75c3 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -39,9 +39,13 @@ snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream); int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, - dma_filter_fn filter_fn, void *filter_data); + struct dma_chan *chan); int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream); +int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, + dma_filter_fn filter_fn, void *filter_data); +int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream); + struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream); /** -- cgit v1.2.3-59-g8ed1b From 71a45cda444f9c47bd63516cf4c24fb6d1ccb151 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:19:49 +0200 Subject: ASoC: Add snd_soc_{add, remove}_platform snd_soc_{add,remove}_platform are similar to snd_soc_register_platform and snd_soc_unregister_platform with the difference that they won't allocate and free the snd_soc_platform structure. Also add snd_soc_lookup_platform which looks up a platform by the device it has been registered for. Signed-off-by: Lars-Peter Clausen Tested-by: Stephen Warren Tested-by: Shawn Guo Signed-off-by: Mark Brown --- include/sound/soc.h | 4 +++ sound/soc/soc-core.c | 85 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 23 deletions(-) (limited to 'include/sound') diff --git a/include/sound/soc.h b/include/sound/soc.h index f619905f0a65..4429254aa195 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -373,6 +373,10 @@ int snd_soc_poweroff(struct device *dev); int snd_soc_register_platform(struct device *dev, const struct snd_soc_platform_driver *platform_drv); void snd_soc_unregister_platform(struct device *dev); +int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform, + const struct snd_soc_platform_driver *platform_drv); +void snd_soc_remove_platform(struct snd_soc_platform *platform); +struct snd_soc_platform *snd_soc_lookup_platform(struct device *dev); int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *codec_drv, struct snd_soc_dai_driver *dai_drv, int num_dai); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4d24b5ea3ba7..0e64af1a7df9 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3901,21 +3901,14 @@ void snd_soc_unregister_dais(struct device *dev, size_t count) EXPORT_SYMBOL_GPL(snd_soc_unregister_dais); /** - * snd_soc_register_platform - Register a platform with the ASoC core - * - * @platform: platform to register + * snd_soc_add_platform - Add a platform to the ASoC core + * @dev: The parent device for the platform + * @platform: The platform to add + * @platform_driver: The driver for the platform */ -int snd_soc_register_platform(struct device *dev, +int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform, const struct snd_soc_platform_driver *platform_drv) { - struct snd_soc_platform *platform; - - dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev)); - - platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL); - if (platform == NULL) - return -ENOMEM; - /* create platform component name */ platform->name = fmt_single_name(dev, &platform->id); if (platform->name == NULL) { @@ -3938,30 +3931,76 @@ int snd_soc_register_platform(struct device *dev, return 0; } -EXPORT_SYMBOL_GPL(snd_soc_register_platform); +EXPORT_SYMBOL_GPL(snd_soc_add_platform); /** - * snd_soc_unregister_platform - Unregister a platform from the ASoC core + * snd_soc_register_platform - Register a platform with the ASoC core * - * @platform: platform to unregister + * @platform: platform to register */ -void snd_soc_unregister_platform(struct device *dev) +int snd_soc_register_platform(struct device *dev, + const struct snd_soc_platform_driver *platform_drv) { struct snd_soc_platform *platform; + int ret; - list_for_each_entry(platform, &platform_list, list) { - if (dev == platform->dev) - goto found; - } - return; + dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev)); -found: + platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL); + if (platform == NULL) + return -ENOMEM; + + ret = snd_soc_add_platform(dev, platform, platform_drv); + if (ret) + kfree(platform); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_register_platform); + +/** + * snd_soc_remove_platform - Remove a platform from the ASoC core + * @platform: the platform to remove + */ +void snd_soc_remove_platform(struct snd_soc_platform *platform) +{ mutex_lock(&client_mutex); list_del(&platform->list); mutex_unlock(&client_mutex); - dev_dbg(dev, "ASoC: Unregistered platform '%s'\n", platform->name); + dev_dbg(platform->dev, "ASoC: Unregistered platform '%s'\n", + platform->name); kfree(platform->name); +} +EXPORT_SYMBOL_GPL(snd_soc_remove_platform); + +struct snd_soc_platform *snd_soc_lookup_platform(struct device *dev) +{ + struct snd_soc_platform *platform; + + list_for_each_entry(platform, &platform_list, list) { + if (dev == platform->dev) + return platform; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_lookup_platform); + +/** + * snd_soc_unregister_platform - Unregister a platform from the ASoC core + * + * @platform: platform to unregister + */ +void snd_soc_unregister_platform(struct device *dev) +{ + struct snd_soc_platform *platform; + + platform = snd_soc_lookup_platform(dev); + if (!platform) + return; + + snd_soc_remove_platform(platform); kfree(platform); } EXPORT_SYMBOL_GPL(snd_soc_unregister_platform); -- cgit v1.2.3-59-g8ed1b From 28c4468b00a1e55e08cc20117de968f7c6275441 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:19:50 +0200 Subject: ASoC: Add a generic dmaengine_pcm driver 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 Tested-by: Stephen Warren Tested-by: Shawn Guo Signed-off-by: Mark Brown --- 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 (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index d015d67e75c3..e0bf24e90669 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 5da8ca7aee05..9e675c76436c 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 99f32f7c0692..197b6ae54c8d 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 000000000000..acfc92698995 --- /dev/null +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2013, Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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]); + } + + 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"); -- cgit v1.2.3-59-g8ed1b From c999836d37c6c1125e856f68877ae13952baa61a Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:19:51 +0200 Subject: ASoC: dmaengine_pcm: Add support for compat platforms Add support for platforms which don't use devicetree yet or have to optionally support a non-devicetree way to request the DMA channel. The patch adds the compat_request_channel and compat_filter_fn callbacks to the snd_dmaengine_pcm_config struct. If the compat_request_channel is implemented it will be used to request the DMA channel. If not dma_request_channel with compat_filter_fn as the filter function will be used to request the channel. The patch also exports the snd_dmaengine_pcm_request_chan() function, since compat platforms will want to use it to request their DMA channel. Signed-off-by: Lars-Peter Clausen Tested-by: Stephen Warren Tested-by: Shawn Guo Signed-off-by: Mark Brown --- include/sound/dmaengine_pcm.h | 29 +++++++++++++++++++++++++++++ sound/soc/soc-dmaengine-pcm.c | 14 ++++++++++++-- sound/soc/soc-generic-dmaengine-pcm.c | 32 +++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 7 deletions(-) (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index e0bf24e90669..1a7897ab3572 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -16,6 +16,7 @@ #define __SOUND_DMAENGINE_PCM_H__ #include +#include #include /** @@ -46,6 +47,8 @@ int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, dma_filter_fn filter_fn, void *filter_data); int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream); +struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn, + void *filter_data); struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream); /** @@ -72,17 +75,43 @@ void snd_dmaengine_pcm_set_config_from_dai_data( const struct snd_dmaengine_dai_dma_data *dma_data, struct dma_slave_config *config); + +/* + * Try to request the DMA channel using compat_request_channel or + * compat_filter_fn if it couldn't be requested through devicetree. + */ +#define SND_DMAENGINE_PCM_FLAG_COMPAT BIT(0) +/* + * Don't try to request the DMA channels through devicetree. This flag only + * makes sense if SND_DMAENGINE_PCM_FLAG_COMPAT is set as well. + */ +#define SND_DMAENGINE_PCM_FLAG_NO_DT BIT(1) + /** * 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. + * @compat_request_channel: Callback to request a DMA channel for platforms + * which do not use devicetree. + * @compat_filter_fn: Will be used as the filter function when requesting a + * channel for platforms which do not use devicetree. The filter parameter + * will be the DAI's DMA data. * @pcm_hardware: snd_pcm_hardware struct to be used for the PCM. * @prealloc_buffer_size: Size of the preallocated audio buffer. + * + * Note: If both compat_request_channel and compat_filter_fn are set + * compat_request_channel will be used to request the channel and + * compat_filter_fn will be ignored. Otherwise the channel will be requested + * using dma_request_channel with compat_filter_fn as the filter function. */ 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); + struct dma_chan *(*compat_request_channel)( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream); + dma_filter_fn compat_filter_fn; const struct snd_pcm_hardware *pcm_hardware; unsigned int prealloc_buffer_size; diff --git a/sound/soc/soc-dmaengine-pcm.c b/sound/soc/soc-dmaengine-pcm.c index b0420a75f412..aa924d9b7986 100644 --- a/sound/soc/soc-dmaengine-pcm.c +++ b/sound/soc/soc-dmaengine-pcm.c @@ -254,7 +254,16 @@ snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer); -static struct dma_chan *dmaengine_pcm_request_channel(dma_filter_fn filter_fn, +/** + * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM + * @filter_fn: Filter function used to request the DMA channel + * @filter_data: Data passed to the DMA filter function + * + * Returns NULL or the requested DMA channel. + * + * This function request a DMA channel for usage with dmaengine PCM. + */ +struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn, void *filter_data) { dma_cap_mask_t mask; @@ -265,6 +274,7 @@ static struct dma_chan *dmaengine_pcm_request_channel(dma_filter_fn filter_fn, return dma_request_channel(mask, filter_fn, filter_data); } +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel); /** * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream @@ -320,7 +330,7 @@ int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, dma_filter_fn filter_fn, void *filter_data) { return snd_dmaengine_pcm_open(substream, - dmaengine_pcm_request_channel(filter_fn, filter_data)); + snd_dmaengine_pcm_request_channel(filter_fn, filter_data)); } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan); diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index acfc92698995..d6e638056389 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -29,6 +29,7 @@ struct dmaengine_pcm { struct dma_chan *chan[SNDRV_PCM_STREAM_CAPTURE + 1]; const struct snd_dmaengine_pcm_config *config; struct snd_soc_platform platform; + bool compat; }; static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p) @@ -121,6 +122,19 @@ static void dmaengine_pcm_free(struct snd_pcm *pcm) snd_pcm_lib_preallocate_free_for_all(pcm); } +static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + + if (pcm->config->compat_request_channel) + return pcm->config->compat_request_channel(rtd, substream); + + return snd_dmaengine_pcm_request_channel(pcm->config->compat_filter_fn, + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream)); +} + static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); @@ -134,6 +148,11 @@ static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd) if (!substream) continue; + if (!pcm->chan[i] && pcm->compat) { + pcm->chan[i] = dmaengine_pcm_compat_request_channel(rtd, + substream); + } + if (!pcm->chan[i]) { dev_err(rtd->platform->dev, "Missing dma channel for stream: %d\n", i); @@ -171,6 +190,7 @@ static const struct snd_soc_platform_driver dmaengine_pcm_platform = { .ops = &dmaengine_pcm_ops, .pcm_new = dmaengine_pcm_new, .pcm_free = dmaengine_pcm_free, + .probe_order = SND_SOC_COMP_ORDER_LATE, }; static const char * const dmaengine_pcm_dma_channel_names[] = { @@ -190,18 +210,20 @@ int snd_dmaengine_pcm_register(struct device *dev, 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, + if (flags & SND_DMAENGINE_PCM_FLAG_COMPAT) + pcm->compat = true; + + if (!(flags & SND_DMAENGINE_PCM_FLAG_NO_DT) && dev->of_node) { + 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]); + } } return snd_soc_add_platform(dev, &pcm->platform, -- cgit v1.2.3-59-g8ed1b From 610f780050090db1af024bd060f819478a656cd0 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:19:55 +0200 Subject: ASoC: dmaengine-pcm: Add support for platforms which can't report residue Unfortunately there are still quite a few platforms with a dmaengine driver which do not support reporting the number of bytes left to transfer. If we want to support these platforms in the generic dmaengine PCM driver we have. Signed-off-by: Lars-Peter Clausen Tested-by: Stephen Warren Tested-by: Shawn Guo Signed-off-by: Mark Brown --- include/sound/dmaengine_pcm.h | 5 +++++ sound/soc/soc-generic-dmaengine-pcm.c | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index 1a7897ab3572..e7052862d6b2 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -86,6 +86,11 @@ void snd_dmaengine_pcm_set_config_from_dai_data( * makes sense if SND_DMAENGINE_PCM_FLAG_COMPAT is set as well. */ #define SND_DMAENGINE_PCM_FLAG_NO_DT BIT(1) +/* + * The platforms dmaengine driver does not support reporting the ammount of + * bytes that are still left to transfer. + */ +#define SND_DMAENGINE_PCM_FLAG_NO_RESIDUE BIT(2) /** * struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index d6e638056389..ae0c37e66ae0 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -193,6 +193,23 @@ static const struct snd_soc_platform_driver dmaengine_pcm_platform = { .probe_order = SND_SOC_COMP_ORDER_LATE, }; +static const struct snd_pcm_ops dmaengine_no_residue_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_no_residue, +}; + +static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform = { + .ops = &dmaengine_no_residue_pcm_ops, + .pcm_new = dmaengine_pcm_new, + .pcm_free = dmaengine_pcm_free, + .probe_order = SND_SOC_COMP_ORDER_LATE, +}; + static const char * const dmaengine_pcm_dma_channel_names[] = { [SNDRV_PCM_STREAM_PLAYBACK] = "tx", [SNDRV_PCM_STREAM_CAPTURE] = "rx", @@ -226,7 +243,11 @@ int snd_dmaengine_pcm_register(struct device *dev, } } - return snd_soc_add_platform(dev, &pcm->platform, + if (flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_soc_add_platform(dev, &pcm->platform, + &dmaengine_no_residue_pcm_platform); + else + return snd_soc_add_platform(dev, &pcm->platform, &dmaengine_pcm_platform); } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register); -- cgit v1.2.3-59-g8ed1b From 22f38f792ec53e2a93be13ecb609bbe911ed8ff9 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 15 Apr 2013 19:20:04 +0200 Subject: ASoC: ux500: Use generic dmaengine PCM Use the generic dmaengine PCM driver instead of a custom implemention. There is a minor functional change, the ux500 PCM driver did not preallocate the audio buffer, while the generic dmaengine PCM driver will do this. Signed-off-by: Lars-Peter Clausen Acked-by: Lee Jones Signed-off-by: Mark Brown --- include/sound/dmaengine_pcm.h | 2 +- sound/soc/ux500/Kconfig | 2 +- sound/soc/ux500/ux500_pcm.c | 159 +++++------------------------------------- 3 files changed, 19 insertions(+), 144 deletions(-) (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index e7052862d6b2..b1d1150c1d60 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -87,7 +87,7 @@ void snd_dmaengine_pcm_set_config_from_dai_data( */ #define SND_DMAENGINE_PCM_FLAG_NO_DT BIT(1) /* - * The platforms dmaengine driver does not support reporting the ammount of + * The platforms dmaengine driver does not support reporting the amount of * bytes that are still left to transfer. */ #define SND_DMAENGINE_PCM_FLAG_NO_RESIDUE BIT(2) diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig index 069330d82be5..c73c5907eb11 100644 --- a/sound/soc/ux500/Kconfig +++ b/sound/soc/ux500/Kconfig @@ -16,7 +16,7 @@ config SND_SOC_UX500_PLAT_MSP_I2S config SND_SOC_UX500_PLAT_DMA tristate "Platform - DB8500 (DMA)" depends on SND_SOC_UX500 - select SND_SOC_DMAENGINE_PCM + select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y if you want to enable the Ux500 platform-driver. diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c index a7d4f04e5964..b6e5ae277299 100644 --- a/sound/soc/ux500/ux500_pcm.c +++ b/sound/soc/ux500/ux500_pcm.c @@ -40,7 +40,7 @@ #define UX500_PLATFORM_PERIODS_MAX 48 #define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE) -static struct snd_pcm_hardware ux500_pcm_hw = { +static const struct snd_pcm_hardware ux500_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_RESUME | @@ -61,43 +61,23 @@ static struct snd_pcm_hardware ux500_pcm_hw = { .periods_max = UX500_PLATFORM_PERIODS_MAX, }; -static void ux500_pcm_dma_hw_free(struct device *dev, - struct snd_pcm_substream *substream) +static struct dma_chan *ux500_pcm_request_chan(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dma_buffer *buf = runtime->dma_buffer_p; - - if (runtime->dma_area == NULL) - return; - - if (buf != &substream->dma_buffer) { - dma_free_coherent(buf->dev.dev, buf->bytes, buf->area, - buf->addr); - kfree(runtime->dma_buffer_p); - } - - snd_pcm_set_runtime_buffer(substream, NULL); -} - -static int ux500_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *dai = rtd->cpu_dai; struct device *dev = dai->dev; - int ret; - struct ux500_msp_dma_params *dma_params; u16 per_data_width, mem_data_width; struct stedma40_chan_cfg *dma_cfg; + struct ux500_msp_dma_params *dma_params; dev_dbg(dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, snd_pcm_stream_str(substream)); - dev_dbg(dev, "%s: Set runtime hwparams.\n", __func__); - snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw); + dma_params = snd_soc_dai_get_dma_data(dai, substream); + dma_cfg = dma_params->dma_cfg; mem_data_width = STEDMA40_HALFWORD_WIDTH; - dma_params = snd_soc_dai_get_dma_data(dai, substream); switch (dma_params->data_size) { case 32: per_data_width = STEDMA40_WORD_WIDTH; @@ -110,13 +90,8 @@ static int ux500_pcm_open(struct snd_pcm_substream *substream) break; default: per_data_width = STEDMA40_WORD_WIDTH; - dev_warn(rtd->platform->dev, - "%s: Unknown data-size (%d)! Assuming 32 bits.\n", - __func__, dma_params->data_size); } - dma_cfg = dma_params->dma_cfg; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { dma_cfg->src_info.data_width = mem_data_width; dma_cfg->dst_info.data_width = per_data_width; @@ -125,123 +100,24 @@ static int ux500_pcm_open(struct snd_pcm_substream *substream) dma_cfg->dst_info.data_width = mem_data_width; } - ret = snd_dmaengine_pcm_open_request_chan(substream, stedma40_filter, - dma_cfg); - if (ret) { - dev_dbg(dai->dev, - "%s: ERROR: snd_dmaengine_pcm_open failed (%d)!\n", - __func__, ret); - return ret; - } - - return 0; -} - -static int ux500_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dma_buffer *buf = runtime->dma_buffer_p; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - int ret = 0; - int size; - - dev_dbg(rtd->platform->dev, "%s: Enter\n", __func__); - - size = params_buffer_bytes(hw_params); - - if (buf) { - if (buf->bytes >= size) - goto out; - ux500_pcm_dma_hw_free(NULL, substream); - } - - if (substream->dma_buffer.area != NULL && - substream->dma_buffer.bytes >= size) { - buf = &substream->dma_buffer; - } else { - buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL); - if (!buf) - goto nomem; - - buf->dev.type = SNDRV_DMA_TYPE_DEV; - buf->dev.dev = NULL; - buf->area = dma_alloc_coherent(NULL, size, &buf->addr, - GFP_KERNEL); - buf->bytes = size; - buf->private_data = NULL; - - if (!buf->area) - goto free; - } - snd_pcm_set_runtime_buffer(substream, buf); - ret = 1; - out: - runtime->dma_bytes = size; - return ret; - - free: - kfree(buf); - nomem: - return -ENOMEM; -} - -static int ux500_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->platform->dev, "%s: Enter\n", __func__); - - ux500_pcm_dma_hw_free(NULL, substream); - - return 0; + return snd_dmaengine_pcm_request_channel(stedma40_filter, dma_cfg); } -static int ux500_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->platform->dev, "%s: Enter.\n", __func__); - - return dma_mmap_coherent(NULL, vma, runtime->dma_area, - runtime->dma_addr, runtime->dma_bytes); -} - -static struct snd_pcm_ops ux500_pcm_ops = { - .open = ux500_pcm_open, - .close = snd_dmaengine_pcm_close_release_chan, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = ux500_pcm_hw_params, - .hw_free = ux500_pcm_hw_free, - .trigger = snd_dmaengine_pcm_trigger, - .pointer = snd_dmaengine_pcm_pointer_no_residue, - .mmap = ux500_pcm_mmap -}; - -int ux500_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_pcm *pcm = rtd->pcm; - - dev_dbg(rtd->platform->dev, "%s: Enter (id = '%s').\n", __func__, - pcm->id); - - pcm->info_flags = 0; - - return 0; -} - -static struct snd_soc_platform_driver ux500_pcm_soc_drv = { - .ops = &ux500_pcm_ops, - .pcm_new = ux500_pcm_new, +static const struct snd_dmaengine_pcm_config ux500_dmaengine_pcm_config = { + .pcm_hardware = &ux500_pcm_hw, + .compat_request_channel = ux500_pcm_request_chan, + .prealloc_buffer_size = 128 * 1024, }; int ux500_pcm_register_platform(struct platform_device *pdev) { int ret; - ret = snd_soc_register_platform(&pdev->dev, &ux500_pcm_soc_drv); + ret = snd_dmaengine_pcm_register(&pdev->dev, + &ux500_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_RESIDUE | + SND_DMAENGINE_PCM_FLAG_COMPAT | + SND_DMAENGINE_PCM_FLAG_NO_DT); if (ret < 0) { dev_err(&pdev->dev, "%s: ERROR: Failed to register platform '%s' (%d)!\n", @@ -255,8 +131,7 @@ EXPORT_SYMBOL_GPL(ux500_pcm_register_platform); int ux500_pcm_unregister_platform(struct platform_device *pdev) { - snd_soc_unregister_platform(&pdev->dev); - + snd_dmaengine_pcm_unregister(&pdev->dev); return 0; } EXPORT_SYMBOL_GPL(ux500_pcm_unregister_platform); -- cgit v1.2.3-59-g8ed1b From d1e1406c6ed0b92200a7de2a09fbab65661dba3c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 20 Apr 2013 19:29:00 +0200 Subject: ASoC: generic-dmaengine-pcm: Add support for half-duplex Some platforms which are half-duplex share the same DMA channel between the playback and capture stream. Add support for this to the generic dmaengine PCM driver. Signed-off-by: Lars-Peter Clausen Tested-by: Shawn Guo Signed-off-by: Mark Brown --- include/sound/dmaengine_pcm.h | 5 ++++ sound/soc/soc-generic-dmaengine-pcm.c | 43 ++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 13 deletions(-) (limited to 'include/sound') diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index b1d1150c1d60..f11c35cd5532 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -91,6 +91,11 @@ void snd_dmaengine_pcm_set_config_from_dai_data( * bytes that are still left to transfer. */ #define SND_DMAENGINE_PCM_FLAG_NO_RESIDUE BIT(2) +/* + * The PCM is half duplex and the DMA channel is shared between capture and + * playback. + */ +#define SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX BIT(3) /** * struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index ae0c37e66ae0..5fd5ed4c0a96 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -29,7 +29,7 @@ struct dmaengine_pcm { struct dma_chan *chan[SNDRV_PCM_STREAM_CAPTURE + 1]; const struct snd_dmaengine_pcm_config *config; struct snd_soc_platform platform; - bool compat; + unsigned int flags; }; static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p) @@ -128,6 +128,9 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( { struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0]) + return pcm->chan[0]; + if (pcm->config->compat_request_channel) return pcm->config->compat_request_channel(rtd, substream); @@ -148,7 +151,7 @@ static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd) if (!substream) continue; - if (!pcm->chan[i] && pcm->compat) { + if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) { pcm->chan[i] = dmaengine_pcm_compat_request_channel(rtd, substream); } @@ -215,6 +218,25 @@ static const char * const dmaengine_pcm_dma_channel_names[] = { [SNDRV_PCM_STREAM_CAPTURE] = "rx", }; +static void dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + struct device_node *of_node) +{ + unsigned int i; + + if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || !of_node) + return; + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) { + pcm->chan[0] = of_dma_request_slave_channel(of_node, "tx_rx"); + pcm->chan[1] = pcm->chan[0]; + } else { + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) { + pcm->chan[i] = of_dma_request_slave_channel(of_node, + dmaengine_pcm_dma_channel_names[i]); + } + } +} + /** * snd_dmaengine_pcm_register - Register a dmaengine based PCM device * @dev: The parent device for the PCM device @@ -225,23 +247,15 @@ 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; pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); if (!pcm) return -ENOMEM; pcm->config = config; + pcm->flags = flags; - if (flags & SND_DMAENGINE_PCM_FLAG_COMPAT) - pcm->compat = true; - - if (!(flags & SND_DMAENGINE_PCM_FLAG_NO_DT) && dev->of_node) { - 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]); - } - } + dmaengine_pcm_request_chan_of(pcm, dev->of_node); if (flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) return snd_soc_add_platform(dev, &pcm->platform, @@ -272,8 +286,11 @@ void snd_dmaengine_pcm_unregister(struct device *dev) pcm = soc_platform_to_pcm(platform); for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) { - if (pcm->chan[i]) + if (pcm->chan[i]) { dma_release_channel(pcm->chan[i]); + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + break; + } } snd_soc_remove_platform(platform); -- cgit v1.2.3-59-g8ed1b