diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2012-02-22 04:49:08 -0500 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-03-02 08:47:25 -0500 |
commit | e7f73a1613567ac82314f33956c0f3810bf1efb2 (patch) | |
tree | 720501188147171460a37efedd5592a28248fdc2 /sound/soc/soc-dmaengine-pcm.c | |
parent | 1355ab147fa38e4b3841469c51422e2343a877b2 (diff) |
ASoC: Add dmaengine PCM helper functions
This patch adds a set of functions which are intended to be used when
implementing a dmaengine based sound PCM driver.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Tested-by: Shawn Guo <shawn.guo@linaro.org>
Acked-by: Vinod Koul <vinod.koul@linux.intel.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/soc-dmaengine-pcm.c')
-rw-r--r-- | sound/soc/soc-dmaengine-pcm.c | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/sound/soc/soc-dmaengine-pcm.c b/sound/soc/soc-dmaengine-pcm.c new file mode 100644 index 000000000000..0526cf82b54f --- /dev/null +++ b/sound/soc/soc-dmaengine-pcm.c | |||
@@ -0,0 +1,287 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012, Analog Devices Inc. | ||
3 | * Author: Lars-Peter Clausen <lars@metafoo.de> | ||
4 | * | ||
5 | * Based on: | ||
6 | * imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> | ||
7 | * mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc. | ||
8 | * ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> | ||
9 | * Copyright (C) 2006 Applied Data Systems | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify it | ||
12 | * under the terms of the GNU General Public License as published by the | ||
13 | * Free Software Foundation; either version 2 of the License, or (at your | ||
14 | * option) any later version. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | * | ||
20 | */ | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/dmaengine.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <sound/pcm.h> | ||
26 | #include <sound/pcm_params.h> | ||
27 | #include <sound/soc.h> | ||
28 | |||
29 | #include <sound/dmaengine_pcm.h> | ||
30 | |||
31 | struct dmaengine_pcm_runtime_data { | ||
32 | struct dma_chan *dma_chan; | ||
33 | |||
34 | unsigned int pos; | ||
35 | |||
36 | void *data; | ||
37 | }; | ||
38 | |||
39 | static inline struct dmaengine_pcm_runtime_data *substream_to_prtd( | ||
40 | const struct snd_pcm_substream *substream) | ||
41 | { | ||
42 | return substream->runtime->private_data; | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * snd_dmaengine_pcm_set_data - Set dmaengine substream private data | ||
47 | * @substream: PCM substream | ||
48 | * @data: Data to set | ||
49 | */ | ||
50 | void snd_dmaengine_pcm_set_data(struct snd_pcm_substream *substream, void *data) | ||
51 | { | ||
52 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
53 | |||
54 | prtd->data = data; | ||
55 | } | ||
56 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_data); | ||
57 | |||
58 | /** | ||
59 | * snd_dmaengine_pcm_get_data - Get dmaeinge substream private data | ||
60 | * @substream: PCM substream | ||
61 | * | ||
62 | * Returns the data previously set with snd_dmaengine_pcm_set_data | ||
63 | */ | ||
64 | void *snd_dmaengine_pcm_get_data(struct snd_pcm_substream *substream) | ||
65 | { | ||
66 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
67 | |||
68 | return prtd->data; | ||
69 | } | ||
70 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_data); | ||
71 | |||
72 | struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream) | ||
73 | { | ||
74 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
75 | |||
76 | return prtd->dma_chan; | ||
77 | } | ||
78 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan); | ||
79 | |||
80 | /** | ||
81 | * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config | ||
82 | * @substream: PCM substream | ||
83 | * @params: hw_params | ||
84 | * @slave_config: DMA slave config | ||
85 | * | ||
86 | * This function can be used to initialize a dma_slave_config from a substream | ||
87 | * and hw_params in a dmaengine based PCM driver implementation. | ||
88 | */ | ||
89 | int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, | ||
90 | const struct snd_pcm_hw_params *params, | ||
91 | struct dma_slave_config *slave_config) | ||
92 | { | ||
93 | enum dma_slave_buswidth buswidth; | ||
94 | |||
95 | switch (params_format(params)) { | ||
96 | case SNDRV_PCM_FORMAT_S8: | ||
97 | buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; | ||
98 | break; | ||
99 | case SNDRV_PCM_FORMAT_S16_LE: | ||
100 | buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; | ||
101 | break; | ||
102 | case SNDRV_PCM_FORMAT_S18_3LE: | ||
103 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
104 | case SNDRV_PCM_FORMAT_S24_LE: | ||
105 | case SNDRV_PCM_FORMAT_S32_LE: | ||
106 | buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; | ||
107 | break; | ||
108 | default: | ||
109 | return -EINVAL; | ||
110 | } | ||
111 | |||
112 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
113 | slave_config->direction = DMA_MEM_TO_DEV; | ||
114 | slave_config->dst_addr_width = buswidth; | ||
115 | } else { | ||
116 | slave_config->direction = DMA_DEV_TO_MEM; | ||
117 | slave_config->src_addr_width = buswidth; | ||
118 | } | ||
119 | |||
120 | return 0; | ||
121 | } | ||
122 | EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config); | ||
123 | |||
124 | static void dmaengine_pcm_dma_complete(void *arg) | ||
125 | { | ||
126 | struct snd_pcm_substream *substream = arg; | ||
127 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
128 | |||
129 | prtd->pos += snd_pcm_lib_period_bytes(substream); | ||
130 | if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream)) | ||
131 | prtd->pos = 0; | ||
132 | |||
133 | snd_pcm_period_elapsed(substream); | ||
134 | } | ||
135 | |||
136 | static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) | ||
137 | { | ||
138 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
139 | struct dma_chan *chan = prtd->dma_chan; | ||
140 | struct dma_async_tx_descriptor *desc; | ||
141 | enum dma_transfer_direction direction; | ||
142 | |||
143 | direction = snd_pcm_substream_to_dma_direction(substream); | ||
144 | |||
145 | desc = chan->device->device_prep_dma_cyclic(chan, | ||
146 | substream->runtime->dma_addr, | ||
147 | snd_pcm_lib_buffer_bytes(substream), | ||
148 | snd_pcm_lib_period_bytes(substream), direction); | ||
149 | |||
150 | if (!desc) | ||
151 | return -ENOMEM; | ||
152 | |||
153 | desc->callback = dmaengine_pcm_dma_complete; | ||
154 | desc->callback_param = substream; | ||
155 | dmaengine_submit(desc); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation | ||
162 | * @substream: PCM substream | ||
163 | * @cmd: Trigger command | ||
164 | * | ||
165 | * Returns 0 on success, a negative error code otherwise. | ||
166 | * | ||
167 | * This function can be used as the PCM trigger callback for dmaengine based PCM | ||
168 | * driver implementations. | ||
169 | */ | ||
170 | int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
171 | { | ||
172 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
173 | int ret; | ||
174 | |||
175 | switch (cmd) { | ||
176 | case SNDRV_PCM_TRIGGER_START: | ||
177 | ret = dmaengine_pcm_prepare_and_submit(substream); | ||
178 | if (ret) | ||
179 | return ret; | ||
180 | dma_async_issue_pending(prtd->dma_chan); | ||
181 | break; | ||
182 | case SNDRV_PCM_TRIGGER_RESUME: | ||
183 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
184 | dmaengine_resume(prtd->dma_chan); | ||
185 | break; | ||
186 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
187 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
188 | dmaengine_pause(prtd->dma_chan); | ||
189 | break; | ||
190 | case SNDRV_PCM_TRIGGER_STOP: | ||
191 | dmaengine_terminate_all(prtd->dma_chan); | ||
192 | break; | ||
193 | default: | ||
194 | return -EINVAL; | ||
195 | } | ||
196 | |||
197 | return 0; | ||
198 | } | ||
199 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger); | ||
200 | |||
201 | /** | ||
202 | * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation | ||
203 | * @substream: PCM substream | ||
204 | * | ||
205 | * This function can be used as the PCM pointer callback for dmaengine based PCM | ||
206 | * driver implementations. | ||
207 | */ | ||
208 | snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) | ||
209 | { | ||
210 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
211 | return bytes_to_frames(substream->runtime, prtd->pos); | ||
212 | } | ||
213 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer); | ||
214 | |||
215 | static int dmaengine_pcm_request_channel(struct dmaengine_pcm_runtime_data *prtd, | ||
216 | dma_filter_fn filter_fn, void *filter_data) | ||
217 | { | ||
218 | dma_cap_mask_t mask; | ||
219 | |||
220 | dma_cap_zero(mask); | ||
221 | dma_cap_set(DMA_SLAVE, mask); | ||
222 | dma_cap_set(DMA_CYCLIC, mask); | ||
223 | prtd->dma_chan = dma_request_channel(mask, filter_fn, filter_data); | ||
224 | |||
225 | if (!prtd->dma_chan) | ||
226 | return -ENXIO; | ||
227 | |||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream | ||
233 | * @substream: PCM substream | ||
234 | * @filter_fn: Filter function used to request the DMA channel | ||
235 | * @filter_data: Data passed to the DMA filter function | ||
236 | * | ||
237 | * Returns 0 on success, a negative error code otherwise. | ||
238 | * | ||
239 | * This function will request a DMA channel using the passed filter function and | ||
240 | * data. The function should usually be called from the pcm open callback. | ||
241 | * | ||
242 | * Note that this function will use private_data field of the substream's | ||
243 | * runtime. So it is not availabe to your pcm driver implementation. If you need | ||
244 | * to keep additional data attached to a substream use | ||
245 | * snd_dmaeinge_pcm_{set,get}_data. | ||
246 | */ | ||
247 | int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, | ||
248 | dma_filter_fn filter_fn, void *filter_data) | ||
249 | { | ||
250 | struct dmaengine_pcm_runtime_data *prtd; | ||
251 | int ret; | ||
252 | |||
253 | ret = snd_pcm_hw_constraint_integer(substream->runtime, | ||
254 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
255 | if (ret < 0) | ||
256 | return ret; | ||
257 | |||
258 | prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); | ||
259 | if (!prtd) | ||
260 | return -ENOMEM; | ||
261 | |||
262 | ret = dmaengine_pcm_request_channel(prtd, filter_fn, filter_data); | ||
263 | if (ret < 0) { | ||
264 | kfree(prtd); | ||
265 | return ret; | ||
266 | } | ||
267 | |||
268 | substream->runtime->private_data = prtd; | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open); | ||
273 | |||
274 | /** | ||
275 | * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream | ||
276 | * @substream: PCM substream | ||
277 | */ | ||
278 | int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) | ||
279 | { | ||
280 | struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); | ||
281 | |||
282 | dma_release_channel(prtd->dma_chan); | ||
283 | kfree(prtd); | ||
284 | |||
285 | return 0; | ||
286 | } | ||
287 | EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); | ||