aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/imx/mx1_mx2-pcm.c
diff options
context:
space:
mode:
authorjavier Martin <javier.martin@vista-silicon.com>2009-08-04 11:17:52 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-08-05 17:31:54 -0400
commitfd6a6394d7d6142afb3d4c87beb3c2c1d25c69bd (patch)
treed2678fcfa7a98b893e295148d618ebdd36992c4f /sound/soc/imx/mx1_mx2-pcm.c
parent07a2039b8eb0af4ff464efd3dfd95de5c02648c6 (diff)
ASoC: add DMA platform driver for MX1x and MX2x
This adds support for DMA platform valid for i.MX1 and i.MX2 platforms. This is not valid for i.MX3 since it doesn't share the same DMA interface than i.MX1 and i.MX2. It has been tested on i.MX27 board. Signed-off-by: Javier Martin <javier.martin@vista-silicon.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/imx/mx1_mx2-pcm.c')
-rw-r--r--sound/soc/imx/mx1_mx2-pcm.c487
1 files changed, 487 insertions, 0 deletions
diff --git a/sound/soc/imx/mx1_mx2-pcm.c b/sound/soc/imx/mx1_mx2-pcm.c
new file mode 100644
index 000000000000..94807f844131
--- /dev/null
+++ b/sound/soc/imx/mx1_mx2-pcm.c
@@ -0,0 +1,487 @@
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
33static 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
47struct 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 */
65static 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 */
96static 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) {
129 printk(KERN_ERR "Error configuring DMA\n");
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 */
155static 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 */
198static int
199snd_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
210static 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) {
219 printk(KERN_ERR "%s: failed to malloc pcm pages\n", __func__);
220 return ret;
221 }
222
223 pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n",
224 __func__, (unsigned int)runtime->dma_addr);
225 pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n",
226 __func__, (unsigned int)runtime->dma_area);
227 pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n",
228 __func__, (unsigned int)runtime->dma_bytes);
229
230 return ret;
231}
232
233static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream)
234{
235 struct snd_pcm_runtime *runtime = substream->runtime;
236 struct mx1_mx2_runtime_data *prtd = runtime->private_data;
237
238 imx_dma_free(prtd->dma_ch);
239
240 snd_pcm_lib_free_pages(substream);
241
242 return 0;
243}
244
245static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
246{
247 struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data;
248 int ret = 0;
249
250 switch (cmd) {
251 case SNDRV_PCM_TRIGGER_START:
252 prtd->tx_spin = 0;
253 /* requested stream startup */
254 prtd->active = 1;
255 pr_debug("%s: starting dma_new_period\n", __func__);
256 ret = dma_new_period(substream);
257 break;
258 case SNDRV_PCM_TRIGGER_STOP:
259 /* requested stream shutdown */
260 pr_debug("%s: stopping dma transfer\n", __func__);
261 ret = audio_stop_dma(substream);
262 break;
263 default:
264 ret = -EINVAL;
265 break;
266 }
267
268 return ret;
269}
270
271static snd_pcm_uframes_t
272mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream)
273{
274 struct snd_pcm_runtime *runtime = substream->runtime;
275 struct mx1_mx2_runtime_data *prtd = runtime->private_data;
276 unsigned int offset = 0;
277
278 /* tx_spin value is used here to check if a transfer is active */
279 if (prtd->tx_spin) {
280 offset = (runtime->period_size * (prtd->periods)) +
281 (runtime->period_size >> 1);
282 if (offset >= runtime->buffer_size)
283 offset = runtime->period_size >> 1;
284 } else {
285 offset = (runtime->period_size * (prtd->periods));
286 if (offset >= runtime->buffer_size)
287 offset = 0;
288 }
289 pr_debug("%s: pointer offset %x\n", __func__, offset);
290
291 return offset;
292}
293
294static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream)
295{
296 struct snd_pcm_runtime *runtime = substream->runtime;
297 struct mx1_mx2_runtime_data *prtd;
298 struct snd_soc_pcm_runtime *rtd = substream->private_data;
299 struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
300 int ret;
301
302 snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware);
303
304 ret = snd_pcm_hw_constraint_integer(runtime,
305 SNDRV_PCM_HW_PARAM_PERIODS);
306 if (ret < 0)
307 return ret;
308
309 prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL);
310 if (prtd == NULL) {
311 ret = -ENOMEM;
312 goto out;
313 }
314
315 runtime->private_data = prtd;
316
317 if (!dma_data)
318 return -ENODEV;
319
320 prtd->dma_params = dma_data;
321
322 pr_debug("%s: Requesting dma channel (%s)\n", __func__,
323 prtd->dma_params->name);
324 prtd->dma_ch = imx_dma_request_by_prio(prtd->dma_params->name,
325 DMA_PRIO_HIGH);
326 if (prtd->dma_ch < 0) {
327 printk(KERN_ERR "Error requesting dma channel\n");
328 return ret;
329 }
330 imx_dma_config_burstlen(prtd->dma_ch,
331 prtd->dma_params->watermark_level);
332
333 ret = imx_dma_config_channel(prtd->dma_ch,
334 prtd->dma_params->per_config,
335 prtd->dma_params->mem_config,
336 prtd->dma_params->event_id, 0);
337
338 if (ret) {
339 pr_debug(KERN_ERR "Error configuring dma channel %d\n",
340 prtd->dma_ch);
341 return ret;
342 }
343
344 pr_debug("%s: Setting tx dma callback function\n", __func__);
345 ret = imx_dma_setup_handlers(prtd->dma_ch,
346 audio_dma_irq, NULL,
347 (void *)substream);
348 if (ret < 0) {
349 printk(KERN_ERR "Error setting dma callback function\n");
350 return ret;
351 }
352 return 0;
353
354 out:
355 return ret;
356}
357
358static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream)
359{
360 struct snd_pcm_runtime *runtime = substream->runtime;
361 struct mx1_mx2_runtime_data *prtd = runtime->private_data;
362
363 kfree(prtd);
364
365 return 0;
366}
367
368static int mx1_mx2_pcm_mmap(struct snd_pcm_substream *substream,
369 struct vm_area_struct *vma)
370{
371 struct snd_pcm_runtime *runtime = substream->runtime;
372 return dma_mmap_writecombine(substream->pcm->card->dev, vma,
373 runtime->dma_area,
374 runtime->dma_addr,
375 runtime->dma_bytes);
376}
377
378struct snd_pcm_ops mx1_mx2_pcm_ops = {
379 .open = mx1_mx2_pcm_open,
380 .close = mx1_mx2_pcm_close,
381 .ioctl = snd_pcm_lib_ioctl,
382 .hw_params = mx1_mx2_pcm_hw_params,
383 .hw_free = mx1_mx2_pcm_hw_free,
384 .prepare = snd_mx1_mx2_prepare,
385 .trigger = mx1_mx2_pcm_trigger,
386 .pointer = mx1_mx2_pcm_pointer,
387 .mmap = mx1_mx2_pcm_mmap,
388};
389
390static u64 mx1_mx2_pcm_dmamask = 0xffffffff;
391
392static int mx1_mx2_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
393{
394 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
395 struct snd_dma_buffer *buf = &substream->dma_buffer;
396 size_t size = mx1_mx2_pcm_hardware.buffer_bytes_max;
397 buf->dev.type = SNDRV_DMA_TYPE_DEV;
398 buf->dev.dev = pcm->card->dev;
399 buf->private_data = NULL;
400
401 /* Reserve uncached-buffered memory area for DMA */
402 buf->area = dma_alloc_writecombine(pcm->card->dev, size,
403 &buf->addr, GFP_KERNEL);
404
405 pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
406 __func__, (void *) buf->area, (void *) buf->addr, size);
407
408 if (!buf->area)
409 return -ENOMEM;
410
411 buf->bytes = size;
412 return 0;
413}
414
415static void mx1_mx2_pcm_free_dma_buffers(struct snd_pcm *pcm)
416{
417 struct snd_pcm_substream *substream;
418 struct snd_dma_buffer *buf;
419 int stream;
420
421 for (stream = 0; stream < 2; stream++) {
422 substream = pcm->streams[stream].substream;
423 if (!substream)
424 continue;
425
426 buf = &substream->dma_buffer;
427 if (!buf->area)
428 continue;
429
430 dma_free_writecombine(pcm->card->dev, buf->bytes,
431 buf->area, buf->addr);
432 buf->area = NULL;
433 }
434}
435
436int mx1_mx2_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
437 struct snd_pcm *pcm)
438{
439 int ret = 0;
440
441 if (!card->dev->dma_mask)
442 card->dev->dma_mask = &mx1_mx2_pcm_dmamask;
443 if (!card->dev->coherent_dma_mask)
444 card->dev->coherent_dma_mask = 0xffffffff;
445
446 if (dai->playback.channels_min) {
447 ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
448 SNDRV_PCM_STREAM_PLAYBACK);
449 pr_debug("%s: preallocate playback buffer\n", __func__);
450 if (ret)
451 goto out;
452 }
453
454 if (dai->capture.channels_min) {
455 ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
456 SNDRV_PCM_STREAM_CAPTURE);
457 pr_debug("%s: preallocate capture buffer\n", __func__);
458 if (ret)
459 goto out;
460 }
461 out:
462 return ret;
463}
464
465struct snd_soc_platform mx1_mx2_soc_platform = {
466 .name = "mx1_mx2-audio",
467 .pcm_ops = &mx1_mx2_pcm_ops,
468 .pcm_new = mx1_mx2_pcm_new,
469 .pcm_free = mx1_mx2_pcm_free_dma_buffers,
470};
471EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform);
472
473static int __init mx1_mx2_soc_platform_init(void)
474{
475 return snd_soc_register_platform(&mx1_mx2_soc_platform);
476}
477module_init(mx1_mx2_soc_platform_init);
478
479static void __exit mx1_mx2_soc_platform_exit(void)
480{
481 snd_soc_unregister_platform(&mx1_mx2_soc_platform);
482}
483module_exit(mx1_mx2_soc_platform_exit);
484
485MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
486MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module");
487MODULE_LICENSE("GPL");