Commit | Line | Data |
---|---|---|
fd6a6394 M |
1 | /* |
2 | * mx1_mx2-pcm.c -- ALSA SoC interface for Freescale i.MX1x, i.MX2x CPUs | |
3 | * | |
4 | * Copyright 2009 Vista Silicon S.L. | |
5 | * Author: Javier Martin | |
6 | * javier.martin@vista-silicon.com | |
7 | * | |
8 | * Based on mxc-pcm.c by Liam Girdwood. | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | * | |
15 | */ | |
16 | ||
17 | #include <linux/module.h> | |
18 | #include <linux/init.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/slab.h> | |
21 | #include <linux/dma-mapping.h> | |
22 | #include <sound/core.h> | |
23 | #include <sound/pcm.h> | |
24 | #include <sound/pcm_params.h> | |
25 | #include <sound/soc.h> | |
26 | #include <asm/dma.h> | |
27 | #include <mach/hardware.h> | |
28 | #include <mach/dma-mx1-mx2.h> | |
29 | ||
30 | #include "mx1_mx2-pcm.h" | |
31 | ||
32 | ||
33 | static const struct snd_pcm_hardware mx1_mx2_pcm_hardware = { | |
34 | .info = (SNDRV_PCM_INFO_INTERLEAVED | | |
35 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
36 | SNDRV_PCM_INFO_MMAP | | |
37 | SNDRV_PCM_INFO_MMAP_VALID), | |
38 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | |
39 | .buffer_bytes_max = 32 * 1024, | |
40 | .period_bytes_min = 64, | |
41 | .period_bytes_max = 8 * 1024, | |
42 | .periods_min = 2, | |
43 | .periods_max = 255, | |
44 | .fifo_size = 0, | |
45 | }; | |
46 | ||
47 | struct mx1_mx2_runtime_data { | |
48 | int dma_ch; | |
49 | int active; | |
50 | unsigned int period; | |
51 | unsigned int periods; | |
52 | int tx_spin; | |
53 | spinlock_t dma_lock; | |
54 | struct mx1_mx2_pcm_dma_params *dma_params; | |
55 | }; | |
56 | ||
57 | ||
58 | /** | |
59 | * This function stops the current dma transfer for playback | |
60 | * and clears the dma pointers. | |
61 | * | |
62 | * @param substream pointer to the structure of the current stream. | |
63 | * | |
64 | */ | |
65 | static int audio_stop_dma(struct snd_pcm_substream *substream) | |
66 | { | |
67 | struct snd_pcm_runtime *runtime = substream->runtime; | |
68 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
69 | unsigned long flags; | |
70 | ||
71 | spin_lock_irqsave(&prtd->dma_lock, flags); | |
72 | ||
73 | pr_debug("%s\n", __func__); | |
74 | ||
75 | prtd->active = 0; | |
76 | prtd->period = 0; | |
77 | prtd->periods = 0; | |
78 | ||
79 | /* this stops the dma channel and clears the buffer ptrs */ | |
80 | ||
81 | imx_dma_disable(prtd->dma_ch); | |
82 | ||
83 | spin_unlock_irqrestore(&prtd->dma_lock, flags); | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
88 | /** | |
89 | * This function is called whenever a new audio block needs to be | |
90 | * transferred to the codec. The function receives the address and the size | |
91 | * of the new block and start a new DMA transfer. | |
92 | * | |
93 | * @param substream pointer to the structure of the current stream. | |
94 | * | |
95 | */ | |
96 | static int dma_new_period(struct snd_pcm_substream *substream) | |
97 | { | |
98 | struct snd_pcm_runtime *runtime = substream->runtime; | |
99 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
100 | unsigned int dma_size; | |
101 | unsigned int offset; | |
102 | int ret = 0; | |
103 | dma_addr_t mem_addr; | |
104 | unsigned int dev_addr; | |
105 | ||
106 | if (prtd->active) { | |
107 | dma_size = frames_to_bytes(runtime, runtime->period_size); | |
108 | offset = dma_size * prtd->period; | |
109 | ||
110 | pr_debug("%s: period (%d) out of (%d)\n", __func__, | |
111 | prtd->period, | |
112 | runtime->periods); | |
113 | pr_debug("period_size %d frames\n offset %d bytes\n", | |
114 | (unsigned int)runtime->period_size, | |
115 | offset); | |
116 | pr_debug("dma_size %d bytes\n", dma_size); | |
117 | ||
118 | snd_BUG_ON(dma_size > mx1_mx2_pcm_hardware.period_bytes_max); | |
119 | ||
120 | mem_addr = (dma_addr_t)(runtime->dma_addr + offset); | |
121 | dev_addr = prtd->dma_params->per_address; | |
122 | pr_debug("%s: mem_addr is %x\n dev_addr is %x\n", | |
123 | __func__, mem_addr, dev_addr); | |
124 | ||
125 | ret = imx_dma_setup_single(prtd->dma_ch, mem_addr, | |
126 | dma_size, dev_addr, | |
127 | prtd->dma_params->transfer_type); | |
128 | if (ret < 0) { | |
fbb474de | 129 | printk(KERN_ERR "Error %d configuring DMA\n", ret); |
fd6a6394 M |
130 | return ret; |
131 | } | |
132 | imx_dma_enable(prtd->dma_ch); | |
133 | ||
134 | pr_debug("%s: transfer enabled\nmem_addr = %x\n", | |
135 | __func__, (unsigned int) mem_addr); | |
136 | pr_debug("dev_addr = %x\ndma_size = %d\n", | |
137 | (unsigned int) dev_addr, dma_size); | |
138 | ||
139 | prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ | |
140 | prtd->period++; | |
141 | prtd->period %= runtime->periods; | |
142 | } | |
143 | return ret; | |
144 | } | |
145 | ||
146 | ||
147 | /** | |
148 | * This is a callback which will be called | |
149 | * when a TX transfer finishes. The call occurs | |
150 | * in interrupt context. | |
151 | * | |
152 | * @param dat pointer to the structure of the current stream. | |
153 | * | |
154 | */ | |
155 | static void audio_dma_irq(int channel, void *data) | |
156 | { | |
157 | struct snd_pcm_substream *substream; | |
158 | struct snd_pcm_runtime *runtime; | |
159 | struct mx1_mx2_runtime_data *prtd; | |
160 | unsigned int dma_size; | |
161 | unsigned int previous_period; | |
162 | unsigned int offset; | |
163 | ||
164 | substream = data; | |
165 | runtime = substream->runtime; | |
166 | prtd = runtime->private_data; | |
167 | previous_period = prtd->periods; | |
168 | dma_size = frames_to_bytes(runtime, runtime->period_size); | |
169 | offset = dma_size * previous_period; | |
170 | ||
171 | prtd->tx_spin = 0; | |
172 | prtd->periods++; | |
173 | prtd->periods %= runtime->periods; | |
174 | ||
175 | pr_debug("%s: irq per %d offset %x\n", __func__, prtd->periods, offset); | |
176 | ||
177 | /* | |
178 | * If we are getting a callback for an active stream then we inform | |
179 | * the PCM middle layer we've finished a period | |
180 | */ | |
181 | if (prtd->active) | |
182 | snd_pcm_period_elapsed(substream); | |
183 | ||
184 | /* | |
185 | * Trig next DMA transfer | |
186 | */ | |
187 | dma_new_period(substream); | |
188 | } | |
189 | ||
190 | /** | |
191 | * This function configures the hardware to allow audio | |
192 | * playback operations. It is called by ALSA framework. | |
193 | * | |
194 | * @param substream pointer to the structure of the current stream. | |
195 | * | |
196 | * @return 0 on success, -1 otherwise. | |
197 | */ | |
198 | static int | |
199 | snd_mx1_mx2_prepare(struct snd_pcm_substream *substream) | |
200 | { | |
201 | struct snd_pcm_runtime *runtime = substream->runtime; | |
202 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
203 | ||
204 | prtd->period = 0; | |
205 | prtd->periods = 0; | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static int mx1_mx2_pcm_hw_params(struct snd_pcm_substream *substream, | |
211 | struct snd_pcm_hw_params *hw_params) | |
212 | { | |
213 | struct snd_pcm_runtime *runtime = substream->runtime; | |
214 | int ret; | |
215 | ||
216 | ret = snd_pcm_lib_malloc_pages(substream, | |
217 | params_buffer_bytes(hw_params)); | |
218 | if (ret < 0) { | |
fbb474de M |
219 | printk(KERN_ERR "%s: Error %d failed to malloc pcm pages \n", |
220 | __func__, ret); | |
fd6a6394 M |
221 | return ret; |
222 | } | |
223 | ||
224 | pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n", | |
225 | __func__, (unsigned int)runtime->dma_addr); | |
226 | pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n", | |
227 | __func__, (unsigned int)runtime->dma_area); | |
228 | pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n", | |
229 | __func__, (unsigned int)runtime->dma_bytes); | |
230 | ||
231 | return ret; | |
232 | } | |
233 | ||
234 | static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream) | |
235 | { | |
236 | struct snd_pcm_runtime *runtime = substream->runtime; | |
237 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
238 | ||
239 | imx_dma_free(prtd->dma_ch); | |
240 | ||
241 | snd_pcm_lib_free_pages(substream); | |
242 | ||
243 | return 0; | |
244 | } | |
245 | ||
246 | static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | |
247 | { | |
248 | struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data; | |
249 | int ret = 0; | |
250 | ||
251 | switch (cmd) { | |
252 | case SNDRV_PCM_TRIGGER_START: | |
253 | prtd->tx_spin = 0; | |
254 | /* requested stream startup */ | |
255 | prtd->active = 1; | |
256 | pr_debug("%s: starting dma_new_period\n", __func__); | |
257 | ret = dma_new_period(substream); | |
258 | break; | |
259 | case SNDRV_PCM_TRIGGER_STOP: | |
260 | /* requested stream shutdown */ | |
261 | pr_debug("%s: stopping dma transfer\n", __func__); | |
262 | ret = audio_stop_dma(substream); | |
263 | break; | |
264 | default: | |
265 | ret = -EINVAL; | |
266 | break; | |
267 | } | |
268 | ||
269 | return ret; | |
270 | } | |
271 | ||
272 | static snd_pcm_uframes_t | |
273 | mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream) | |
274 | { | |
275 | struct snd_pcm_runtime *runtime = substream->runtime; | |
276 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
277 | unsigned int offset = 0; | |
278 | ||
279 | /* tx_spin value is used here to check if a transfer is active */ | |
280 | if (prtd->tx_spin) { | |
281 | offset = (runtime->period_size * (prtd->periods)) + | |
282 | (runtime->period_size >> 1); | |
283 | if (offset >= runtime->buffer_size) | |
284 | offset = runtime->period_size >> 1; | |
285 | } else { | |
286 | offset = (runtime->period_size * (prtd->periods)); | |
287 | if (offset >= runtime->buffer_size) | |
288 | offset = 0; | |
289 | } | |
290 | pr_debug("%s: pointer offset %x\n", __func__, offset); | |
291 | ||
292 | return offset; | |
293 | } | |
294 | ||
295 | static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream) | |
296 | { | |
297 | struct snd_pcm_runtime *runtime = substream->runtime; | |
298 | struct mx1_mx2_runtime_data *prtd; | |
299 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
300 | struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data; | |
301 | int ret; | |
302 | ||
303 | snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware); | |
304 | ||
305 | ret = snd_pcm_hw_constraint_integer(runtime, | |
306 | SNDRV_PCM_HW_PARAM_PERIODS); | |
307 | if (ret < 0) | |
308 | return ret; | |
309 | ||
310 | prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL); | |
311 | if (prtd == NULL) { | |
312 | ret = -ENOMEM; | |
313 | goto out; | |
314 | } | |
315 | ||
316 | runtime->private_data = prtd; | |
317 | ||
318 | if (!dma_data) | |
319 | return -ENODEV; | |
320 | ||
321 | prtd->dma_params = dma_data; | |
322 | ||
323 | pr_debug("%s: Requesting dma channel (%s)\n", __func__, | |
324 | prtd->dma_params->name); | |
325 | prtd->dma_ch = imx_dma_request_by_prio(prtd->dma_params->name, | |
326 | DMA_PRIO_HIGH); | |
327 | if (prtd->dma_ch < 0) { | |
fbb474de | 328 | printk(KERN_ERR "Error %d requesting dma channel\n", ret); |
fd6a6394 M |
329 | return ret; |
330 | } | |
331 | imx_dma_config_burstlen(prtd->dma_ch, | |
332 | prtd->dma_params->watermark_level); | |
333 | ||
334 | ret = imx_dma_config_channel(prtd->dma_ch, | |
335 | prtd->dma_params->per_config, | |
336 | prtd->dma_params->mem_config, | |
337 | prtd->dma_params->event_id, 0); | |
338 | ||
339 | if (ret) { | |
fbb474de M |
340 | pr_debug(KERN_ERR "Error %d configuring dma channel %d\n", |
341 | ret, prtd->dma_ch); | |
fd6a6394 M |
342 | return ret; |
343 | } | |
344 | ||
345 | pr_debug("%s: Setting tx dma callback function\n", __func__); | |
346 | ret = imx_dma_setup_handlers(prtd->dma_ch, | |
347 | audio_dma_irq, NULL, | |
348 | (void *)substream); | |
349 | if (ret < 0) { | |
fbb474de | 350 | printk(KERN_ERR "Error %d setting dma callback function\n", ret); |
fd6a6394 M |
351 | return ret; |
352 | } | |
353 | return 0; | |
354 | ||
355 | out: | |
356 | return ret; | |
357 | } | |
358 | ||
359 | static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream) | |
360 | { | |
361 | struct snd_pcm_runtime *runtime = substream->runtime; | |
362 | struct mx1_mx2_runtime_data *prtd = runtime->private_data; | |
363 | ||
364 | kfree(prtd); | |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
369 | static int mx1_mx2_pcm_mmap(struct snd_pcm_substream *substream, | |
370 | struct vm_area_struct *vma) | |
371 | { | |
372 | struct snd_pcm_runtime *runtime = substream->runtime; | |
373 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | |
374 | runtime->dma_area, | |
375 | runtime->dma_addr, | |
376 | runtime->dma_bytes); | |
377 | } | |
378 | ||
d555a552 | 379 | static struct snd_pcm_ops mx1_mx2_pcm_ops = { |
fd6a6394 M |
380 | .open = mx1_mx2_pcm_open, |
381 | .close = mx1_mx2_pcm_close, | |
382 | .ioctl = snd_pcm_lib_ioctl, | |
383 | .hw_params = mx1_mx2_pcm_hw_params, | |
384 | .hw_free = mx1_mx2_pcm_hw_free, | |
385 | .prepare = snd_mx1_mx2_prepare, | |
386 | .trigger = mx1_mx2_pcm_trigger, | |
387 | .pointer = mx1_mx2_pcm_pointer, | |
388 | .mmap = mx1_mx2_pcm_mmap, | |
389 | }; | |
390 | ||
391 | static u64 mx1_mx2_pcm_dmamask = 0xffffffff; | |
392 | ||
393 | static int mx1_mx2_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | |
394 | { | |
395 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | |
396 | struct snd_dma_buffer *buf = &substream->dma_buffer; | |
397 | size_t size = mx1_mx2_pcm_hardware.buffer_bytes_max; | |
398 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | |
399 | buf->dev.dev = pcm->card->dev; | |
400 | buf->private_data = NULL; | |
401 | ||
402 | /* Reserve uncached-buffered memory area for DMA */ | |
403 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | |
404 | &buf->addr, GFP_KERNEL); | |
405 | ||
406 | pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", | |
407 | __func__, (void *) buf->area, (void *) buf->addr, size); | |
408 | ||
409 | if (!buf->area) | |
410 | return -ENOMEM; | |
411 | ||
412 | buf->bytes = size; | |
413 | return 0; | |
414 | } | |
415 | ||
416 | static void mx1_mx2_pcm_free_dma_buffers(struct snd_pcm *pcm) | |
417 | { | |
418 | struct snd_pcm_substream *substream; | |
419 | struct snd_dma_buffer *buf; | |
420 | int stream; | |
421 | ||
422 | for (stream = 0; stream < 2; stream++) { | |
423 | substream = pcm->streams[stream].substream; | |
424 | if (!substream) | |
425 | continue; | |
426 | ||
427 | buf = &substream->dma_buffer; | |
428 | if (!buf->area) | |
429 | continue; | |
430 | ||
431 | dma_free_writecombine(pcm->card->dev, buf->bytes, | |
432 | buf->area, buf->addr); | |
433 | buf->area = NULL; | |
434 | } | |
435 | } | |
436 | ||
d555a552 | 437 | static int mx1_mx2_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, |
fd6a6394 M |
438 | struct snd_pcm *pcm) |
439 | { | |
440 | int ret = 0; | |
441 | ||
442 | if (!card->dev->dma_mask) | |
443 | card->dev->dma_mask = &mx1_mx2_pcm_dmamask; | |
444 | if (!card->dev->coherent_dma_mask) | |
445 | card->dev->coherent_dma_mask = 0xffffffff; | |
446 | ||
447 | if (dai->playback.channels_min) { | |
448 | ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm, | |
449 | SNDRV_PCM_STREAM_PLAYBACK); | |
450 | pr_debug("%s: preallocate playback buffer\n", __func__); | |
451 | if (ret) | |
452 | goto out; | |
453 | } | |
454 | ||
455 | if (dai->capture.channels_min) { | |
456 | ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm, | |
457 | SNDRV_PCM_STREAM_CAPTURE); | |
458 | pr_debug("%s: preallocate capture buffer\n", __func__); | |
459 | if (ret) | |
460 | goto out; | |
461 | } | |
462 | out: | |
463 | return ret; | |
464 | } | |
465 | ||
466 | struct snd_soc_platform mx1_mx2_soc_platform = { | |
467 | .name = "mx1_mx2-audio", | |
468 | .pcm_ops = &mx1_mx2_pcm_ops, | |
469 | .pcm_new = mx1_mx2_pcm_new, | |
470 | .pcm_free = mx1_mx2_pcm_free_dma_buffers, | |
471 | }; | |
472 | EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform); | |
473 | ||
474 | static int __init mx1_mx2_soc_platform_init(void) | |
475 | { | |
476 | return snd_soc_register_platform(&mx1_mx2_soc_platform); | |
477 | } | |
478 | module_init(mx1_mx2_soc_platform_init); | |
479 | ||
480 | static void __exit mx1_mx2_soc_platform_exit(void) | |
481 | { | |
482 | snd_soc_unregister_platform(&mx1_mx2_soc_platform); | |
483 | } | |
484 | module_exit(mx1_mx2_soc_platform_exit); | |
485 | ||
486 | MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com"); | |
487 | MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module"); | |
488 | MODULE_LICENSE("GPL"); |