Commit | Line | Data |
---|---|---|
2ba28053 FE |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // Freescale ASRC ALSA SoC Platform (DMA) driver | |
4 | // | |
5 | // Copyright (C) 2014 Freescale Semiconductor, Inc. | |
6 | // | |
7 | // Author: Nicolin Chen <nicoleotsuka@gmail.com> | |
3117bb31 NC |
8 | |
9 | #include <linux/dma-mapping.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/platform_data/dma-imx.h> | |
12 | #include <sound/dmaengine_pcm.h> | |
13 | #include <sound/pcm_params.h> | |
14 | ||
15 | #include "fsl_asrc.h" | |
16 | ||
17 | #define FSL_ASRC_DMABUF_SIZE (256 * 1024) | |
18 | ||
f77bb3b7 | 19 | static const struct snd_pcm_hardware snd_imx_hardware = { |
3117bb31 NC |
20 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
21 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
22 | SNDRV_PCM_INFO_MMAP | | |
23 | SNDRV_PCM_INFO_MMAP_VALID | | |
24 | SNDRV_PCM_INFO_PAUSE | | |
25 | SNDRV_PCM_INFO_RESUME, | |
26 | .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, | |
27 | .period_bytes_min = 128, | |
28 | .period_bytes_max = 65535, /* Limited by SDMA engine */ | |
29 | .periods_min = 2, | |
30 | .periods_max = 255, | |
31 | .fifo_size = 0, | |
32 | }; | |
33 | ||
34 | static bool filter(struct dma_chan *chan, void *param) | |
35 | { | |
36 | if (!imx_dma_is_general_purpose(chan)) | |
37 | return false; | |
38 | ||
39 | chan->private = param; | |
40 | ||
41 | return true; | |
42 | } | |
43 | ||
44 | static void fsl_asrc_dma_complete(void *arg) | |
45 | { | |
46 | struct snd_pcm_substream *substream = arg; | |
47 | struct snd_pcm_runtime *runtime = substream->runtime; | |
48 | struct fsl_asrc_pair *pair = runtime->private_data; | |
49 | ||
50 | pair->pos += snd_pcm_lib_period_bytes(substream); | |
51 | if (pair->pos >= snd_pcm_lib_buffer_bytes(substream)) | |
52 | pair->pos = 0; | |
53 | ||
54 | snd_pcm_period_elapsed(substream); | |
55 | } | |
56 | ||
57 | static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) | |
58 | { | |
59 | u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; | |
60 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
61 | struct snd_pcm_runtime *runtime = substream->runtime; | |
62 | struct fsl_asrc_pair *pair = runtime->private_data; | |
7f110661 KM |
63 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); |
64 | struct device *dev = component->dev; | |
3117bb31 NC |
65 | unsigned long flags = DMA_CTRL_ACK; |
66 | ||
67 | /* Prepare and submit Front-End DMA channel */ | |
68 | if (!substream->runtime->no_period_wakeup) | |
69 | flags |= DMA_PREP_INTERRUPT; | |
70 | ||
71 | pair->pos = 0; | |
72 | pair->desc[!dir] = dmaengine_prep_dma_cyclic( | |
73 | pair->dma_chan[!dir], runtime->dma_addr, | |
74 | snd_pcm_lib_buffer_bytes(substream), | |
75 | snd_pcm_lib_period_bytes(substream), | |
24dbd9ed | 76 | dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); |
3117bb31 NC |
77 | if (!pair->desc[!dir]) { |
78 | dev_err(dev, "failed to prepare slave DMA for Front-End\n"); | |
79 | return -ENOMEM; | |
80 | } | |
81 | ||
82 | pair->desc[!dir]->callback = fsl_asrc_dma_complete; | |
83 | pair->desc[!dir]->callback_param = substream; | |
84 | ||
85 | dmaengine_submit(pair->desc[!dir]); | |
86 | ||
87 | /* Prepare and submit Back-End DMA channel */ | |
88 | pair->desc[dir] = dmaengine_prep_dma_cyclic( | |
89 | pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); | |
90 | if (!pair->desc[dir]) { | |
91 | dev_err(dev, "failed to prepare slave DMA for Back-End\n"); | |
92 | return -ENOMEM; | |
93 | } | |
94 | ||
95 | dmaengine_submit(pair->desc[dir]); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) | |
101 | { | |
102 | struct snd_pcm_runtime *runtime = substream->runtime; | |
103 | struct fsl_asrc_pair *pair = runtime->private_data; | |
104 | int ret; | |
105 | ||
106 | switch (cmd) { | |
107 | case SNDRV_PCM_TRIGGER_START: | |
108 | case SNDRV_PCM_TRIGGER_RESUME: | |
109 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
110 | ret = fsl_asrc_dma_prepare_and_submit(substream); | |
111 | if (ret) | |
112 | return ret; | |
113 | dma_async_issue_pending(pair->dma_chan[IN]); | |
114 | dma_async_issue_pending(pair->dma_chan[OUT]); | |
115 | break; | |
116 | case SNDRV_PCM_TRIGGER_STOP: | |
117 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
118 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
119 | dmaengine_terminate_all(pair->dma_chan[OUT]); | |
120 | dmaengine_terminate_all(pair->dma_chan[IN]); | |
121 | break; | |
122 | default: | |
123 | return -EINVAL; | |
124 | } | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, | |
130 | struct snd_pcm_hw_params *params) | |
131 | { | |
132 | enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; | |
133 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
134 | bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; | |
135 | struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; | |
136 | struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; | |
7f110661 | 137 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); |
3117bb31 NC |
138 | struct snd_pcm_runtime *runtime = substream->runtime; |
139 | struct fsl_asrc_pair *pair = runtime->private_data; | |
140 | struct fsl_asrc *asrc_priv = pair->asrc_priv; | |
141 | struct dma_slave_config config_fe, config_be; | |
142 | enum asrc_pair_index index = pair->index; | |
7f110661 | 143 | struct device *dev = component->dev; |
3117bb31 NC |
144 | int stream = substream->stream; |
145 | struct imx_dma_data *tmp_data; | |
146 | struct snd_soc_dpcm *dpcm; | |
147 | struct dma_chan *tmp_chan; | |
148 | struct device *dev_be; | |
149 | u8 dir = tx ? OUT : IN; | |
150 | dma_cap_mask_t mask; | |
151 | int ret; | |
152 | ||
153 | /* Fetch the Back-End dma_data from DPCM */ | |
8d6258a4 | 154 | for_each_dpcm_be(rtd, stream, dpcm) { |
3117bb31 NC |
155 | struct snd_soc_pcm_runtime *be = dpcm->be; |
156 | struct snd_pcm_substream *substream_be; | |
157 | struct snd_soc_dai *dai = be->cpu_dai; | |
158 | ||
159 | if (dpcm->fe != rtd) | |
160 | continue; | |
161 | ||
162 | substream_be = snd_soc_dpcm_get_substream(be, stream); | |
163 | dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); | |
164 | dev_be = dai->dev; | |
165 | break; | |
166 | } | |
167 | ||
168 | if (!dma_params_be) { | |
169 | dev_err(dev, "failed to get the substream of Back-End\n"); | |
170 | return -EINVAL; | |
171 | } | |
172 | ||
173 | /* Override dma_data of the Front-End and config its dmaengine */ | |
174 | dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | |
175 | dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index); | |
176 | dma_params_fe->maxburst = dma_params_be->maxburst; | |
177 | ||
178 | pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir); | |
179 | if (!pair->dma_chan[!dir]) { | |
180 | dev_err(dev, "failed to request DMA channel\n"); | |
181 | return -EINVAL; | |
182 | } | |
183 | ||
184 | memset(&config_fe, 0, sizeof(config_fe)); | |
185 | ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); | |
186 | if (ret) { | |
187 | dev_err(dev, "failed to prepare DMA config for Front-End\n"); | |
188 | return ret; | |
189 | } | |
190 | ||
191 | ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe); | |
192 | if (ret) { | |
193 | dev_err(dev, "failed to config DMA channel for Front-End\n"); | |
194 | return ret; | |
195 | } | |
196 | ||
197 | /* Request and config DMA channel for Back-End */ | |
198 | dma_cap_zero(mask); | |
199 | dma_cap_set(DMA_SLAVE, mask); | |
200 | dma_cap_set(DMA_CYCLIC, mask); | |
201 | ||
202 | /* Get DMA request of Back-End */ | |
203 | tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); | |
204 | tmp_data = tmp_chan->private; | |
205 | pair->dma_data.dma_request = tmp_data->dma_request; | |
206 | dma_release_channel(tmp_chan); | |
207 | ||
208 | /* Get DMA request of Front-End */ | |
209 | tmp_chan = fsl_asrc_get_dma_channel(pair, dir); | |
210 | tmp_data = tmp_chan->private; | |
211 | pair->dma_data.dma_request2 = tmp_data->dma_request; | |
212 | pair->dma_data.peripheral_type = tmp_data->peripheral_type; | |
213 | pair->dma_data.priority = tmp_data->priority; | |
214 | dma_release_channel(tmp_chan); | |
215 | ||
216 | pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); | |
217 | if (!pair->dma_chan[dir]) { | |
218 | dev_err(dev, "failed to request DMA channel for Back-End\n"); | |
219 | return -EINVAL; | |
220 | } | |
221 | ||
222 | if (asrc_priv->asrc_width == 16) | |
223 | buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; | |
224 | else | |
225 | buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; | |
226 | ||
227 | config_be.direction = DMA_DEV_TO_DEV; | |
228 | config_be.src_addr_width = buswidth; | |
229 | config_be.src_maxburst = dma_params_be->maxburst; | |
230 | config_be.dst_addr_width = buswidth; | |
231 | config_be.dst_maxburst = dma_params_be->maxburst; | |
232 | ||
233 | if (tx) { | |
234 | config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index); | |
235 | config_be.dst_addr = dma_params_be->addr; | |
236 | } else { | |
237 | config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index); | |
238 | config_be.src_addr = dma_params_be->addr; | |
239 | } | |
240 | ||
241 | ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be); | |
242 | if (ret) { | |
243 | dev_err(dev, "failed to config DMA channel for Back-End\n"); | |
244 | return ret; | |
245 | } | |
246 | ||
247 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | |
248 | ||
249 | return 0; | |
250 | } | |
251 | ||
252 | static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream) | |
253 | { | |
254 | struct snd_pcm_runtime *runtime = substream->runtime; | |
255 | struct fsl_asrc_pair *pair = runtime->private_data; | |
256 | ||
257 | snd_pcm_set_runtime_buffer(substream, NULL); | |
258 | ||
259 | if (pair->dma_chan[IN]) | |
260 | dma_release_channel(pair->dma_chan[IN]); | |
261 | ||
262 | if (pair->dma_chan[OUT]) | |
263 | dma_release_channel(pair->dma_chan[OUT]); | |
264 | ||
265 | pair->dma_chan[IN] = NULL; | |
266 | pair->dma_chan[OUT] = NULL; | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
271 | static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) | |
272 | { | |
273 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
274 | struct snd_pcm_runtime *runtime = substream->runtime; | |
7f110661 KM |
275 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); |
276 | struct device *dev = component->dev; | |
3117bb31 NC |
277 | struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); |
278 | struct fsl_asrc_pair *pair; | |
279 | ||
280 | pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); | |
443be77e | 281 | if (!pair) |
3117bb31 | 282 | return -ENOMEM; |
3117bb31 NC |
283 | |
284 | pair->asrc_priv = asrc_priv; | |
285 | ||
286 | runtime->private_data = pair; | |
287 | ||
288 | snd_pcm_hw_constraint_integer(substream->runtime, | |
289 | SNDRV_PCM_HW_PARAM_PERIODS); | |
290 | snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); | |
291 | ||
292 | return 0; | |
293 | } | |
294 | ||
295 | static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream) | |
296 | { | |
297 | struct snd_pcm_runtime *runtime = substream->runtime; | |
298 | struct fsl_asrc_pair *pair = runtime->private_data; | |
6ccf62c7 NC |
299 | struct fsl_asrc *asrc_priv; |
300 | ||
301 | if (!pair) | |
302 | return 0; | |
303 | ||
304 | asrc_priv = pair->asrc_priv; | |
3117bb31 | 305 | |
6ccf62c7 | 306 | if (asrc_priv->pair[pair->index] == pair) |
3117bb31 NC |
307 | asrc_priv->pair[pair->index] = NULL; |
308 | ||
309 | kfree(pair); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream) | |
315 | { | |
316 | struct snd_pcm_runtime *runtime = substream->runtime; | |
317 | struct fsl_asrc_pair *pair = runtime->private_data; | |
318 | ||
319 | return bytes_to_frames(substream->runtime, pair->pos); | |
320 | } | |
321 | ||
115c7254 | 322 | static const struct snd_pcm_ops fsl_asrc_dma_pcm_ops = { |
3117bb31 NC |
323 | .ioctl = snd_pcm_lib_ioctl, |
324 | .hw_params = fsl_asrc_dma_hw_params, | |
325 | .hw_free = fsl_asrc_dma_hw_free, | |
326 | .trigger = fsl_asrc_dma_trigger, | |
327 | .open = fsl_asrc_dma_startup, | |
328 | .close = fsl_asrc_dma_shutdown, | |
329 | .pointer = fsl_asrc_dma_pcm_pointer, | |
330 | }; | |
331 | ||
332 | static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) | |
333 | { | |
334 | struct snd_card *card = rtd->card->snd_card; | |
335 | struct snd_pcm_substream *substream; | |
336 | struct snd_pcm *pcm = rtd->pcm; | |
337 | int ret, i; | |
338 | ||
339 | ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); | |
340 | if (ret) { | |
341 | dev_err(card->dev, "failed to set DMA mask\n"); | |
342 | return ret; | |
343 | } | |
344 | ||
345 | for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { | |
346 | substream = pcm->streams[i].substream; | |
347 | if (!substream) | |
348 | continue; | |
349 | ||
350 | ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, | |
351 | FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer); | |
352 | if (ret) { | |
353 | dev_err(card->dev, "failed to allocate DMA buffer\n"); | |
354 | goto err; | |
355 | } | |
356 | } | |
357 | ||
358 | return 0; | |
359 | ||
360 | err: | |
361 | if (--i == 0 && pcm->streams[i].substream) | |
362 | snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); | |
363 | ||
364 | return ret; | |
365 | } | |
366 | ||
367 | static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm) | |
368 | { | |
369 | struct snd_pcm_substream *substream; | |
370 | int i; | |
371 | ||
372 | for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { | |
373 | substream = pcm->streams[i].substream; | |
374 | if (!substream) | |
375 | continue; | |
376 | ||
377 | snd_dma_free_pages(&substream->dma_buffer); | |
378 | substream->dma_buffer.area = NULL; | |
379 | substream->dma_buffer.addr = 0; | |
380 | } | |
381 | } | |
382 | ||
7f110661 KM |
383 | struct snd_soc_component_driver fsl_asrc_component = { |
384 | .name = DRV_NAME, | |
3117bb31 NC |
385 | .ops = &fsl_asrc_dma_pcm_ops, |
386 | .pcm_new = fsl_asrc_dma_pcm_new, | |
387 | .pcm_free = fsl_asrc_dma_pcm_free, | |
388 | }; | |
7f110661 | 389 | EXPORT_SYMBOL_GPL(fsl_asrc_component); |