aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2013-04-15 13:19:50 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2013-04-17 09:21:25 -0400
commit28c4468b00a1e55e08cc20117de968f7c6275441 (patch)
tree47bd04d6a4e80c632ddfae202e6aa0c3375f21ea
parent71a45cda444f9c47bd63516cf4c24fb6d1ccb151 (diff)
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 <lars@metafoo.de> Tested-by: Stephen Warren <swarren@nvidia.com> Tested-by: Shawn Guo <shawn.guo@linaro.org> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
-rw-r--r--include/sound/dmaengine_pcm.h25
-rw-r--r--sound/soc/Kconfig4
-rw-r--r--sound/soc/Makefile4
-rw-r--r--sound/soc/soc-generic-dmaengine-pcm.c241
4 files changed, 274 insertions, 0 deletions
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(
72 const struct snd_dmaengine_dai_dma_data *dma_data, 72 const struct snd_dmaengine_dai_dma_data *dma_data,
73 struct dma_slave_config *config); 73 struct dma_slave_config *config);
74 74
75/**
76 * struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM
77 * @prepare_slave_config: Callback used to fill in the DMA slave_config for a
78 * PCM substream. Will be called from the PCM drivers hwparams callback.
79 * @pcm_hardware: snd_pcm_hardware struct to be used for the PCM.
80 * @prealloc_buffer_size: Size of the preallocated audio buffer.
81 */
82struct snd_dmaengine_pcm_config {
83 int (*prepare_slave_config)(struct snd_pcm_substream *substream,
84 struct snd_pcm_hw_params *params,
85 struct dma_slave_config *slave_config);
86
87 const struct snd_pcm_hardware *pcm_hardware;
88 unsigned int prealloc_buffer_size;
89};
90
91int snd_dmaengine_pcm_register(struct device *dev,
92 const struct snd_dmaengine_pcm_config *config,
93 unsigned int flags);
94void snd_dmaengine_pcm_unregister(struct device *dev);
95
96int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
97 struct snd_pcm_hw_params *params,
98 struct dma_slave_config *slave_config);
99
75#endif 100#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
29config SND_SOC_DMAENGINE_PCM 29config SND_SOC_DMAENGINE_PCM
30 bool 30 bool
31 31
32config SND_SOC_GENERIC_DMAENGINE_PCM
33 bool
34 select SND_SOC_DMAENGINE_PCM
35
32# All the supported SoCs 36# All the supported SoCs
33source "sound/soc/atmel/Kconfig" 37source "sound/soc/atmel/Kconfig"
34source "sound/soc/au1x/Kconfig" 38source "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),)
5snd-soc-core-objs += soc-dmaengine-pcm.o 5snd-soc-core-objs += soc-dmaengine-pcm.o
6endif 6endif
7 7
8ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
9snd-soc-core-objs += soc-generic-dmaengine-pcm.o
10endif
11
8obj-$(CONFIG_SND_SOC) += snd-soc-core.o 12obj-$(CONFIG_SND_SOC) += snd-soc-core.o
9obj-$(CONFIG_SND_SOC) += codecs/ 13obj-$(CONFIG_SND_SOC) += codecs/
10obj-$(CONFIG_SND_SOC) += generic/ 14obj-$(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 @@
1/*
2 * Copyright (C) 2013, Analog Devices Inc.
3 * Author: Lars-Peter Clausen <lars@metafoo.de>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * You should have received a copy of the GNU General Public License along
11 * with this program; if not, write to the Free Software Foundation, Inc.,
12 * 675 Mass Ave, Cambridge, MA 02139, USA.
13 *
14 */
15#include <linux/module.h>
16#include <linux/init.h>
17#include <linux/dmaengine.h>
18#include <linux/slab.h>
19#include <sound/pcm.h>
20#include <sound/pcm_params.h>
21#include <sound/soc.h>
22#include <linux/dma-mapping.h>
23#include <linux/of.h>
24#include <linux/of_dma.h>
25
26#include <sound/dmaengine_pcm.h>
27
28struct dmaengine_pcm {
29 struct dma_chan *chan[SNDRV_PCM_STREAM_CAPTURE + 1];
30 const struct snd_dmaengine_pcm_config *config;
31 struct snd_soc_platform platform;
32};
33
34static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p)
35{
36 return container_of(p, struct dmaengine_pcm, platform);
37}
38
39/**
40 * snd_dmaengine_pcm_prepare_slave_config() - Generic prepare_slave_config callback
41 * @substream: PCM substream
42 * @params: hw_params
43 * @slave_config: DMA slave config to prepare
44 *
45 * This function can be used as a generic prepare_slave_config callback for
46 * platforms which make use of the snd_dmaengine_dai_dma_data struct for their
47 * DAI DMA data. Internally the function will first call
48 * snd_hwparams_to_dma_slave_config to fill in the slave config based on the
49 * hw_params, followed by snd_dmaengine_set_config_from_dai_data to fill in the
50 * remaining fields based on the DAI DMA data.
51 */
52int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
53 struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
54{
55 struct snd_soc_pcm_runtime *rtd = substream->private_data;
56 struct snd_dmaengine_dai_dma_data *dma_data;
57 int ret;
58
59 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
60
61 ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
62 if (ret)
63 return ret;
64
65 snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data,
66 slave_config);
67
68 return 0;
69}
70EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_prepare_slave_config);
71
72static int dmaengine_pcm_hw_params(struct snd_pcm_substream *substream,
73 struct snd_pcm_hw_params *params)
74{
75 struct snd_soc_pcm_runtime *rtd = substream->private_data;
76 struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
77 struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
78 struct dma_slave_config slave_config;
79 int ret;
80
81 if (pcm->config->prepare_slave_config) {
82 ret = pcm->config->prepare_slave_config(substream, params,
83 &slave_config);
84 if (ret)
85 return ret;
86
87 ret = dmaengine_slave_config(chan, &slave_config);
88 if (ret)
89 return ret;
90 }
91
92 return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
93}
94
95static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
96{
97 struct snd_soc_pcm_runtime *rtd = substream->private_data;
98 struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
99 struct dma_chan *chan = pcm->chan[substream->stream];
100 int ret;
101
102 ret = snd_soc_set_runtime_hwparams(substream,
103 pcm->config->pcm_hardware);
104 if (ret)
105 return ret;
106
107 return snd_dmaengine_pcm_open(substream, chan);
108}
109
110static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm,
111 struct snd_pcm_substream *substream)
112{
113 if (!pcm->chan[substream->stream])
114 return NULL;
115
116 return pcm->chan[substream->stream]->device->dev;
117}
118
119static void dmaengine_pcm_free(struct snd_pcm *pcm)
120{
121 snd_pcm_lib_preallocate_free_for_all(pcm);
122}
123
124static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
125{
126 struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
127 const struct snd_dmaengine_pcm_config *config = pcm->config;
128 struct snd_pcm_substream *substream;
129 unsigned int i;
130 int ret;
131
132 for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
133 substream = rtd->pcm->streams[i].substream;
134 if (!substream)
135 continue;
136
137 if (!pcm->chan[i]) {
138 dev_err(rtd->platform->dev,
139 "Missing dma channel for stream: %d\n", i);
140 ret = -EINVAL;
141 goto err_free;
142 }
143
144 ret = snd_pcm_lib_preallocate_pages(substream,
145 SNDRV_DMA_TYPE_DEV,
146 dmaengine_dma_dev(pcm, substream),
147 config->prealloc_buffer_size,
148 config->pcm_hardware->buffer_bytes_max);
149 if (ret)
150 goto err_free;
151 }
152
153 return 0;
154
155err_free:
156 dmaengine_pcm_free(rtd->pcm);
157 return ret;
158}
159
160static const struct snd_pcm_ops dmaengine_pcm_ops = {
161 .open = dmaengine_pcm_open,
162 .close = snd_dmaengine_pcm_close,
163 .ioctl = snd_pcm_lib_ioctl,
164 .hw_params = dmaengine_pcm_hw_params,
165 .hw_free = snd_pcm_lib_free_pages,
166 .trigger = snd_dmaengine_pcm_trigger,
167 .pointer = snd_dmaengine_pcm_pointer,
168};
169
170static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
171 .ops = &dmaengine_pcm_ops,
172 .pcm_new = dmaengine_pcm_new,
173 .pcm_free = dmaengine_pcm_free,
174};
175
176static const char * const dmaengine_pcm_dma_channel_names[] = {
177 [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
178 [SNDRV_PCM_STREAM_CAPTURE] = "rx",
179};
180
181/**
182 * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
183 * @dev: The parent device for the PCM device
184 * @config: Platform specific PCM configuration
185 * @flags: Platform specific quirks
186 */
187int snd_dmaengine_pcm_register(struct device *dev,
188 const struct snd_dmaengine_pcm_config *config, unsigned int flags)
189{
190 struct dmaengine_pcm *pcm;
191 unsigned int i;
192
193 if (!dev->of_node)
194 return -EINVAL;
195
196 pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
197 if (!pcm)
198 return -ENOMEM;
199
200 pcm->config = config;
201
202 for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
203 pcm->chan[i] = of_dma_request_slave_channel(dev->of_node,
204 dmaengine_pcm_dma_channel_names[i]);
205 }
206
207 return snd_soc_add_platform(dev, &pcm->platform,
208 &dmaengine_pcm_platform);
209}
210EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);
211
212/**
213 * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
214 * @dev: Parent device the PCM was register with
215 *
216 * Removes a dmaengine based PCM device previously registered with
217 * snd_dmaengine_pcm_register.
218 */
219void snd_dmaengine_pcm_unregister(struct device *dev)
220{
221 struct snd_soc_platform *platform;
222 struct dmaengine_pcm *pcm;
223 unsigned int i;
224
225 platform = snd_soc_lookup_platform(dev);
226 if (!platform)
227 return;
228
229 pcm = soc_platform_to_pcm(platform);
230
231 for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
232 if (pcm->chan[i])
233 dma_release_channel(pcm->chan[i]);
234 }
235
236 snd_soc_remove_platform(platform);
237 kfree(pcm);
238}
239EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister);
240
241MODULE_LICENSE("GPL");