aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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");