diff options
-rw-r--r-- | include/sound/dmaengine_pcm.h | 25 | ||||
-rw-r--r-- | sound/soc/Kconfig | 4 | ||||
-rw-r--r-- | sound/soc/Makefile | 4 | ||||
-rw-r--r-- | sound/soc/soc-generic-dmaengine-pcm.c | 241 |
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 | */ | ||
82 | struct 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 | |||
91 | int snd_dmaengine_pcm_register(struct device *dev, | ||
92 | const struct snd_dmaengine_pcm_config *config, | ||
93 | unsigned int flags); | ||
94 | void snd_dmaengine_pcm_unregister(struct device *dev); | ||
95 | |||
96 | int 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 | |||
29 | config SND_SOC_DMAENGINE_PCM | 29 | config SND_SOC_DMAENGINE_PCM |
30 | bool | 30 | bool |
31 | 31 | ||
32 | config SND_SOC_GENERIC_DMAENGINE_PCM | ||
33 | bool | ||
34 | select SND_SOC_DMAENGINE_PCM | ||
35 | |||
32 | # All the supported SoCs | 36 | # All the supported SoCs |
33 | source "sound/soc/atmel/Kconfig" | 37 | source "sound/soc/atmel/Kconfig" |
34 | source "sound/soc/au1x/Kconfig" | 38 | 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),) | |||
5 | snd-soc-core-objs += soc-dmaengine-pcm.o | 5 | snd-soc-core-objs += soc-dmaengine-pcm.o |
6 | endif | 6 | endif |
7 | 7 | ||
8 | ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),) | ||
9 | snd-soc-core-objs += soc-generic-dmaengine-pcm.o | ||
10 | endif | ||
11 | |||
8 | obj-$(CONFIG_SND_SOC) += snd-soc-core.o | 12 | obj-$(CONFIG_SND_SOC) += snd-soc-core.o |
9 | obj-$(CONFIG_SND_SOC) += codecs/ | 13 | obj-$(CONFIG_SND_SOC) += codecs/ |
10 | obj-$(CONFIG_SND_SOC) += generic/ | 14 | 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 @@ | |||
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 | |||
28 | struct 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 | |||
34 | static 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 | */ | ||
52 | int 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 | } | ||
70 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_prepare_slave_config); | ||
71 | |||
72 | static 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 | |||
95 | static 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 | |||
110 | static 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 | |||
119 | static void dmaengine_pcm_free(struct snd_pcm *pcm) | ||
120 | { | ||
121 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
122 | } | ||
123 | |||
124 | static 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 | |||
155 | err_free: | ||
156 | dmaengine_pcm_free(rtd->pcm); | ||
157 | return ret; | ||
158 | } | ||
159 | |||
160 | static 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 | |||
170 | static 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 | |||
176 | static 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 | */ | ||
187 | int 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 | } | ||
210 | EXPORT_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 | */ | ||
219 | void 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 | } | ||
239 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister); | ||
240 | |||
241 | MODULE_LICENSE("GPL"); | ||