diff options
author | Ben Dooks <ben@simtec.co.uk> | 2007-02-14 07:20:03 -0500 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2007-05-11 10:55:37 -0400 |
commit | c0f41bb1717ae31f806615e81b808753271dd3d9 (patch) | |
tree | 20bbb0cd90dc9a6b09b6c8fbba353d9d9677e2e2 /sound/soc/s3c24xx/s3c24xx-pcm.c | |
parent | c1422a6658ef6101fc5a979021487c732cb177a1 (diff) |
[ALSA] ASoC Samsung S3C24xx audio DMA
This patch by Ben Dooks from Simtec Electronics adds ASoC audio DMA
support for the Samsung S3C24xx CPU.
Signed-off-by: Ben Dooks <ben@simtec.co.uk>
Signed-off-by: Graeme Gregory <gg@opensource.wolfsonmicro.com>
Signed-off-by: Liam Girdwood <lg@opensource.wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/soc/s3c24xx/s3c24xx-pcm.c')
-rw-r--r-- | sound/soc/s3c24xx/s3c24xx-pcm.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c new file mode 100644 index 000000000000..f1c0b9fa179c --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c | |||
@@ -0,0 +1,462 @@ | |||
1 | /* | ||
2 | * s3c24xx-pcm.c -- ALSA Soc Audio Layer | ||
3 | * | ||
4 | * (c) 2006 Wolfson Microelectronics PLC. | ||
5 | * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com | ||
6 | * | ||
7 | * (c) 2004-2005 Simtec Electronics | ||
8 | * http://armlinux.simtec.co.uk/ | ||
9 | * Ben Dooks <ben@simtec.co.uk> | ||
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 | * Revision history | ||
17 | * 11th Dec 2006 Merged with Simtec driver | ||
18 | * 10th Nov 2006 Initial version. | ||
19 | */ | ||
20 | |||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/dma-mapping.h> | ||
26 | |||
27 | #include <sound/driver.h> | ||
28 | #include <sound/core.h> | ||
29 | #include <sound/pcm.h> | ||
30 | #include <sound/pcm_params.h> | ||
31 | #include <sound/soc.h> | ||
32 | |||
33 | #include <asm/dma.h> | ||
34 | #include <asm/io.h> | ||
35 | #include <asm/hardware.h> | ||
36 | #include <asm/arch/dma.h> | ||
37 | #include <asm/arch/audio.h> | ||
38 | |||
39 | #include "s3c24xx-pcm.h" | ||
40 | |||
41 | #define S3C24XX_PCM_DEBUG 0 | ||
42 | #if S3C24XX_PCM_DEBUG | ||
43 | #define DBG(x...) printk(KERN_DEBUG x) | ||
44 | #else | ||
45 | #define DBG(x...) | ||
46 | #endif | ||
47 | |||
48 | static const struct snd_pcm_hardware s3c24xx_pcm_hardware = { | ||
49 | .info = SNDRV_PCM_INFO_INTERLEAVED | | ||
50 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
51 | SNDRV_PCM_INFO_MMAP | | ||
52 | SNDRV_PCM_INFO_MMAP_VALID, | ||
53 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | ||
54 | SNDRV_PCM_FMTBIT_U16_LE | | ||
55 | SNDRV_PCM_FMTBIT_U8 | | ||
56 | SNDRV_PCM_FMTBIT_S8, | ||
57 | .channels_min = 2, | ||
58 | .channels_max = 2, | ||
59 | .buffer_bytes_max = 128*1024, | ||
60 | .period_bytes_min = PAGE_SIZE, | ||
61 | .period_bytes_max = PAGE_SIZE*2, | ||
62 | .periods_min = 2, | ||
63 | .periods_max = 128, | ||
64 | .fifo_size = 32, | ||
65 | }; | ||
66 | |||
67 | struct s3c24xx_runtime_data { | ||
68 | spinlock_t lock; | ||
69 | int state; | ||
70 | unsigned int dma_loaded; | ||
71 | unsigned int dma_limit; | ||
72 | unsigned int dma_period; | ||
73 | dma_addr_t dma_start; | ||
74 | dma_addr_t dma_pos; | ||
75 | dma_addr_t dma_end; | ||
76 | struct s3c24xx_pcm_dma_params *params; | ||
77 | }; | ||
78 | |||
79 | /* s3c24xx_pcm_enqueue | ||
80 | * | ||
81 | * place a dma buffer onto the queue for the dma system | ||
82 | * to handle. | ||
83 | */ | ||
84 | static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream) | ||
85 | { | ||
86 | struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; | ||
87 | dma_addr_t pos = prtd->dma_pos; | ||
88 | int ret; | ||
89 | |||
90 | DBG("Entered %s\n", __FUNCTION__); | ||
91 | |||
92 | while ( prtd->dma_loaded < prtd->dma_limit) { | ||
93 | unsigned long len = prtd->dma_period; | ||
94 | |||
95 | DBG("dma_loaded: %d\n",prtd->dma_loaded); | ||
96 | |||
97 | if ((pos + len) > prtd->dma_end) { | ||
98 | len = prtd->dma_end - pos; | ||
99 | DBG(KERN_DEBUG "%s: corrected dma len %ld\n", | ||
100 | __FUNCTION__, len); | ||
101 | } | ||
102 | |||
103 | ret = s3c2410_dma_enqueue(prtd->params->channel, substream, pos, len); | ||
104 | |||
105 | if (ret == 0) { | ||
106 | prtd->dma_loaded++; | ||
107 | pos += prtd->dma_period; | ||
108 | if (pos >= prtd->dma_end) | ||
109 | pos = prtd->dma_start; | ||
110 | } else | ||
111 | break; | ||
112 | } | ||
113 | |||
114 | prtd->dma_pos = pos; | ||
115 | } | ||
116 | |||
117 | static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel, | ||
118 | void *dev_id, int size, | ||
119 | enum s3c2410_dma_buffresult result) | ||
120 | { | ||
121 | struct snd_pcm_substream *substream = dev_id; | ||
122 | struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; | ||
123 | |||
124 | DBG("Entered %s\n", __FUNCTION__); | ||
125 | |||
126 | if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR) | ||
127 | return; | ||
128 | |||
129 | if (substream) | ||
130 | snd_pcm_period_elapsed(substream); | ||
131 | |||
132 | spin_lock(&prtd->lock); | ||
133 | if (prtd->state & ST_RUNNING) { | ||
134 | prtd->dma_loaded--; | ||
135 | s3c24xx_pcm_enqueue(substream); | ||
136 | } | ||
137 | |||
138 | spin_unlock(&prtd->lock); | ||
139 | } | ||
140 | |||
141 | static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
142 | struct snd_pcm_hw_params *params) | ||
143 | { | ||
144 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
145 | struct s3c24xx_runtime_data *prtd = runtime->private_data; | ||
146 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
147 | struct s3c24xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; | ||
148 | unsigned long totbytes = params_buffer_bytes(params); | ||
149 | int ret=0; | ||
150 | |||
151 | DBG("Entered %s\n", __FUNCTION__); | ||
152 | |||
153 | /* return if this is a bufferless transfer e.g. | ||
154 | * codec <--> BT codec or GSM modem -- lg FIXME */ | ||
155 | if (!dma) | ||
156 | return 0; | ||
157 | |||
158 | /* prepare DMA */ | ||
159 | prtd->params = dma; | ||
160 | |||
161 | DBG("params %p, client %p, channel %d\n", prtd->params, | ||
162 | prtd->params->client, prtd->params->channel); | ||
163 | |||
164 | ret = s3c2410_dma_request(prtd->params->channel, | ||
165 | prtd->params->client, NULL); | ||
166 | |||
167 | if (ret) { | ||
168 | DBG(KERN_ERR "failed to get dma channel\n"); | ||
169 | return ret; | ||
170 | } | ||
171 | |||
172 | /* channel needs configuring for mem=>device, increment memory addr, | ||
173 | * sync to pclk, half-word transfers to the IIS-FIFO. */ | ||
174 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
175 | s3c2410_dma_devconfig(prtd->params->channel, | ||
176 | S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | | ||
177 | S3C2410_DISRCC_APB, prtd->params->dma_addr); | ||
178 | |||
179 | s3c2410_dma_config(prtd->params->channel, | ||
180 | 2, S3C2410_DCON_SYNC_PCLK | S3C2410_DCON_HANDSHAKE); | ||
181 | } else { | ||
182 | s3c2410_dma_config(prtd->params->channel, | ||
183 | 2, S3C2410_DCON_HANDSHAKE | S3C2410_DCON_SYNC_PCLK); | ||
184 | |||
185 | s3c2410_dma_devconfig(prtd->params->channel, | ||
186 | S3C2410_DMASRC_HW, 0x3, | ||
187 | prtd->params->dma_addr); | ||
188 | } | ||
189 | |||
190 | s3c2410_dma_set_buffdone_fn(prtd->params->channel, | ||
191 | s3c24xx_audio_buffdone); | ||
192 | |||
193 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
194 | |||
195 | runtime->dma_bytes = totbytes; | ||
196 | |||
197 | spin_lock_irq(&prtd->lock); | ||
198 | prtd->dma_loaded = 0; | ||
199 | prtd->dma_limit = runtime->hw.periods_min; | ||
200 | prtd->dma_period = params_period_bytes(params); | ||
201 | prtd->dma_start = runtime->dma_addr; | ||
202 | prtd->dma_pos = prtd->dma_start; | ||
203 | prtd->dma_end = prtd->dma_start + totbytes; | ||
204 | spin_unlock_irq(&prtd->lock); | ||
205 | |||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static int s3c24xx_pcm_hw_free(struct snd_pcm_substream *substream) | ||
210 | { | ||
211 | struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; | ||
212 | |||
213 | DBG("Entered %s\n", __FUNCTION__); | ||
214 | |||
215 | /* TODO - do we need to ensure DMA flushed */ | ||
216 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
217 | |||
218 | if(prtd->params) { | ||
219 | s3c2410_dma_free(prtd->params->channel, prtd->params->client); | ||
220 | prtd->params = NULL; | ||
221 | } | ||
222 | |||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) | ||
227 | { | ||
228 | struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; | ||
229 | int ret = 0; | ||
230 | |||
231 | DBG("Entered %s\n", __FUNCTION__); | ||
232 | |||
233 | /* return if this is a bufferless transfer e.g. | ||
234 | * codec <--> BT codec or GSM modem -- lg FIXME */ | ||
235 | if (!prtd->params) | ||
236 | return 0; | ||
237 | |||
238 | /* flush the DMA channel */ | ||
239 | s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); | ||
240 | prtd->dma_loaded = 0; | ||
241 | prtd->dma_pos = prtd->dma_start; | ||
242 | |||
243 | /* enqueue dma buffers */ | ||
244 | s3c24xx_pcm_enqueue(substream); | ||
245 | |||
246 | return ret; | ||
247 | } | ||
248 | |||
249 | static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
250 | { | ||
251 | struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; | ||
252 | int ret = 0; | ||
253 | |||
254 | DBG("Entered %s\n", __FUNCTION__); | ||
255 | |||
256 | spin_lock(&prtd->lock); | ||
257 | |||
258 | switch (cmd) { | ||
259 | case SNDRV_PCM_TRIGGER_START: | ||
260 | case SNDRV_PCM_TRIGGER_RESUME: | ||
261 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
262 | prtd->state |= ST_RUNNING; | ||
263 | s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START); | ||
264 | s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STARTED); | ||
265 | break; | ||
266 | |||
267 | case SNDRV_PCM_TRIGGER_STOP: | ||
268 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
269 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
270 | prtd->state &= ~ST_RUNNING; | ||
271 | s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP); | ||
272 | break; | ||
273 | |||
274 | default: | ||
275 | ret = -EINVAL; | ||
276 | break; | ||
277 | } | ||
278 | |||
279 | spin_unlock(&prtd->lock); | ||
280 | |||
281 | return ret; | ||
282 | } | ||
283 | |||
284 | static snd_pcm_uframes_t s3c24xx_pcm_pointer(struct snd_pcm_substream *substream) | ||
285 | { | ||
286 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
287 | struct s3c24xx_runtime_data *prtd = runtime->private_data; | ||
288 | unsigned long res; | ||
289 | dma_addr_t src, dst; | ||
290 | |||
291 | DBG("Entered %s\n", __FUNCTION__); | ||
292 | |||
293 | spin_lock(&prtd->lock); | ||
294 | s3c2410_dma_getposition(prtd->params->channel, &src, &dst); | ||
295 | |||
296 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | ||
297 | res = dst - prtd->dma_start; | ||
298 | else | ||
299 | res = src - prtd->dma_start; | ||
300 | |||
301 | spin_unlock(&prtd->lock); | ||
302 | |||
303 | DBG("Pointer %x %x\n",src,dst); | ||
304 | |||
305 | /* we seem to be getting the odd error from the pcm library due | ||
306 | * to out-of-bounds pointers. this is maybe due to the dma engine | ||
307 | * not having loaded the new values for the channel before being | ||
308 | * callled... (todo - fix ) | ||
309 | */ | ||
310 | |||
311 | if (res >= snd_pcm_lib_buffer_bytes(substream)) { | ||
312 | if (res == snd_pcm_lib_buffer_bytes(substream)) | ||
313 | res = 0; | ||
314 | } | ||
315 | |||
316 | return bytes_to_frames(substream->runtime, res); | ||
317 | } | ||
318 | |||
319 | static int s3c24xx_pcm_open(struct snd_pcm_substream *substream) | ||
320 | { | ||
321 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
322 | struct s3c24xx_runtime_data *prtd; | ||
323 | |||
324 | int ret; | ||
325 | |||
326 | DBG("Entered %s\n", __FUNCTION__); | ||
327 | |||
328 | snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); | ||
329 | |||
330 | prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL); | ||
331 | if (prtd == NULL) | ||
332 | return -ENOMEM; | ||
333 | |||
334 | runtime->private_data = prtd; | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | static int s3c24xx_pcm_close(struct snd_pcm_substream *substream) | ||
339 | { | ||
340 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
341 | struct s3c24xx_runtime_data *prtd = runtime->private_data; | ||
342 | |||
343 | DBG("Entered %s\n", __FUNCTION__); | ||
344 | |||
345 | if(prtd) | ||
346 | kfree(prtd); | ||
347 | else | ||
348 | DBG("s3c24xx_pcm_close called with prtd == NULL\n"); | ||
349 | |||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream, | ||
354 | struct vm_area_struct *vma) | ||
355 | { | ||
356 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
357 | |||
358 | DBG("Entered %s\n", __FUNCTION__); | ||
359 | |||
360 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | ||
361 | runtime->dma_area, | ||
362 | runtime->dma_addr, | ||
363 | runtime->dma_bytes); | ||
364 | } | ||
365 | |||
366 | static struct snd_pcm_ops s3c24xx_pcm_ops = { | ||
367 | .open = s3c24xx_pcm_open, | ||
368 | .close = s3c24xx_pcm_close, | ||
369 | .ioctl = snd_pcm_lib_ioctl, | ||
370 | .hw_params = s3c24xx_pcm_hw_params, | ||
371 | .hw_free = s3c24xx_pcm_hw_free, | ||
372 | .prepare = s3c24xx_pcm_prepare, | ||
373 | .trigger = s3c24xx_pcm_trigger, | ||
374 | .pointer = s3c24xx_pcm_pointer, | ||
375 | .mmap = s3c24xx_pcm_mmap, | ||
376 | }; | ||
377 | |||
378 | static int s3c24xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
379 | { | ||
380 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
381 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
382 | size_t size = s3c24xx_pcm_hardware.buffer_bytes_max; | ||
383 | |||
384 | DBG("Entered %s\n", __FUNCTION__); | ||
385 | |||
386 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
387 | buf->dev.dev = pcm->card->dev; | ||
388 | buf->private_data = NULL; | ||
389 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | ||
390 | &buf->addr, GFP_KERNEL); | ||
391 | if (!buf->area) | ||
392 | return -ENOMEM; | ||
393 | buf->bytes = size; | ||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | ||
398 | { | ||
399 | struct snd_pcm_substream *substream; | ||
400 | struct snd_dma_buffer *buf; | ||
401 | int stream; | ||
402 | |||
403 | DBG("Entered %s\n", __FUNCTION__); | ||
404 | |||
405 | for (stream = 0; stream < 2; stream++) { | ||
406 | substream = pcm->streams[stream].substream; | ||
407 | if (!substream) | ||
408 | continue; | ||
409 | |||
410 | buf = &substream->dma_buffer; | ||
411 | if (!buf->area) | ||
412 | continue; | ||
413 | |||
414 | dma_free_writecombine(pcm->card->dev, buf->bytes, | ||
415 | buf->area, buf->addr); | ||
416 | buf->area = NULL; | ||
417 | } | ||
418 | } | ||
419 | |||
420 | static u64 s3c24xx_pcm_dmamask = DMA_32BIT_MASK; | ||
421 | |||
422 | static int s3c24xx_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, | ||
423 | struct snd_pcm *pcm) | ||
424 | { | ||
425 | int ret = 0; | ||
426 | |||
427 | DBG("Entered %s\n", __FUNCTION__); | ||
428 | |||
429 | if (!card->dev->dma_mask) | ||
430 | card->dev->dma_mask = &s3c24xx_pcm_dmamask; | ||
431 | if (!card->dev->coherent_dma_mask) | ||
432 | card->dev->coherent_dma_mask = 0xffffffff; | ||
433 | |||
434 | if (dai->playback.channels_min) { | ||
435 | ret = s3c24xx_pcm_preallocate_dma_buffer(pcm, | ||
436 | SNDRV_PCM_STREAM_PLAYBACK); | ||
437 | if (ret) | ||
438 | goto out; | ||
439 | } | ||
440 | |||
441 | if (dai->capture.channels_min) { | ||
442 | ret = s3c24xx_pcm_preallocate_dma_buffer(pcm, | ||
443 | SNDRV_PCM_STREAM_CAPTURE); | ||
444 | if (ret) | ||
445 | goto out; | ||
446 | } | ||
447 | out: | ||
448 | return ret; | ||
449 | } | ||
450 | |||
451 | struct snd_soc_platform s3c24xx_soc_platform = { | ||
452 | .name = "s3c24xx-audio", | ||
453 | .pcm_ops = &s3c24xx_pcm_ops, | ||
454 | .pcm_new = s3c24xx_pcm_new, | ||
455 | .pcm_free = s3c24xx_pcm_free_dma_buffers, | ||
456 | }; | ||
457 | |||
458 | EXPORT_SYMBOL_GPL(s3c24xx_soc_platform); | ||
459 | |||
460 | MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); | ||
461 | MODULE_DESCRIPTION("Samsung S3C24XX PCM DMA module"); | ||
462 | MODULE_LICENSE("GPL"); | ||