aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc
diff options
context:
space:
mode:
authorFrank Mandarino <fmandarino@endrelia.com>2006-10-06 12:39:29 -0400
committerJaroslav Kysela <perex@suse.cz>2007-02-09 03:00:26 -0500
commitff9abf5b0a655b59d59ea61aec5be6285bf3ac30 (patch)
tree8aeb05baa3ffcaa412b7e0c85cdd651815fc1882 /sound/soc
parent7f137ab673124ee0a210ab5b74c1f7234d6145fa (diff)
[ALSA] ASoC AT91RM92000 audio DMA
This patch adds ASoC audio DMA support to the Atmel AT91RM9200 CPU. Features:- o Playback/Capture supported. o 16 Bit data size. Signed-off-by: Frank Mandarino <fmandarino@endrelia.com> Signed-off-by: Liam Girdwood <liam.girdwood@wolfsonmicro.com> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/soc')
-rw-r--r--sound/soc/at91/at91rm9200-pcm.c428
-rw-r--r--sound/soc/at91/at91rm9200-pcm.h75
2 files changed, 503 insertions, 0 deletions
diff --git a/sound/soc/at91/at91rm9200-pcm.c b/sound/soc/at91/at91rm9200-pcm.c
new file mode 100644
index 00000000000..237bc5f0757
--- /dev/null
+++ b/sound/soc/at91/at91rm9200-pcm.c
@@ -0,0 +1,428 @@
1/*
2 * at91rm9200-pcm.c -- ALSA PCM interface for the Atmel AT91RM9200 chip.
3 *
4 * Author: Frank Mandarino <fmandarino@endrelia.com>
5 * Endrelia Technologies Inc.
6 * Created: Mar 3, 2006
7 *
8 * Based on pxa2xx-pcm.c by:
9 *
10 * Author: Nicolas Pitre
11 * Created: Nov 30, 2004
12 * Copyright: (C) 2004 MontaVista Software, Inc.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License version 2 as
16 * published by the Free Software Foundation.
17 */
18
19#include <linux/module.h>
20#include <linux/init.h>
21#include <linux/platform_device.h>
22#include <linux/slab.h>
23#include <linux/dma-mapping.h>
24
25#include <sound/driver.h>
26#include <sound/core.h>
27#include <sound/pcm.h>
28#include <sound/pcm_params.h>
29#include <sound/soc.h>
30
31#include <asm/arch/at91rm9200.h>
32#include <asm/arch/at91rm9200_ssc.h>
33#include <asm/arch/at91rm9200_pdc.h>
34#include <asm/arch/hardware.h>
35
36#include "at91rm9200-pcm.h"
37
38#if 0
39#define DBG(x...) printk(KERN_INFO "at91rm9200-pcm: " x)
40#else
41#define DBG(x...)
42#endif
43
44static const snd_pcm_hardware_t at91rm9200_pcm_hardware = {
45 .info = SNDRV_PCM_INFO_MMAP |
46 SNDRV_PCM_INFO_MMAP_VALID |
47 SNDRV_PCM_INFO_INTERLEAVED |
48 SNDRV_PCM_INFO_PAUSE,
49 .formats = SNDRV_PCM_FMTBIT_S16_LE,
50 .period_bytes_min = 32,
51 .period_bytes_max = 8192,
52 .periods_min = 2,
53 .periods_max = 1024,
54 .buffer_bytes_max = 32 * 1024,
55};
56
57struct at91rm9200_runtime_data {
58 at91rm9200_pcm_dma_params_t *params;
59 dma_addr_t dma_buffer; /* physical address of dma buffer */
60 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
61 size_t period_size;
62 dma_addr_t period_ptr; /* physical address of next period */
63 u32 pdc_xpr_save; /* PDC register save */
64 u32 pdc_xcr_save;
65 u32 pdc_xnpr_save;
66 u32 pdc_xncr_save;
67};
68
69static void at91rm9200_pcm_dma_irq(u32 ssc_sr,
70 struct snd_pcm_substream *substream)
71{
72 struct at91rm9200_runtime_data *prtd = substream->runtime->private_data;
73 at91rm9200_pcm_dma_params_t *params = prtd->params;
74 static int count = 0;
75
76 count++;
77
78 if (ssc_sr & params->mask->ssc_endbuf) {
79
80 printk(KERN_WARNING
81 "at91rm9200-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
82 substream->stream == SNDRV_PCM_STREAM_PLAYBACK
83 ? "underrun" : "overrun",
84 params->name, ssc_sr, count);
85
86 /* re-start the PDC */
87 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_disable);
88
89 prtd->period_ptr += prtd->period_size;
90 if (prtd->period_ptr >= prtd->dma_buffer_end) {
91 prtd->period_ptr = prtd->dma_buffer;
92 }
93
94 at91_ssc_write(params->pdc->xpr, prtd->period_ptr);
95 at91_ssc_write(params->pdc->xcr,
96 prtd->period_size / params->pdc_xfer_size);
97
98 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_enable);
99 }
100
101 if (ssc_sr & params->mask->ssc_endx) {
102
103 /* Load the PDC next pointer and counter registers */
104 prtd->period_ptr += prtd->period_size;
105 if (prtd->period_ptr >= prtd->dma_buffer_end) {
106 prtd->period_ptr = prtd->dma_buffer;
107 }
108 at91_ssc_write(params->pdc->xnpr, prtd->period_ptr);
109 at91_ssc_write(params->pdc->xncr,
110 prtd->period_size / params->pdc_xfer_size);
111 }
112
113 snd_pcm_period_elapsed(substream);
114}
115
116static int at91rm9200_pcm_hw_params(struct snd_pcm_substream *substream,
117 struct snd_pcm_hw_params *params)
118{
119 snd_pcm_runtime_t *runtime = substream->runtime;
120 struct at91rm9200_runtime_data *prtd = runtime->private_data;
121 struct snd_soc_pcm_runtime *rtd = substream->private_data;
122
123 /* this may get called several times by oss emulation
124 * with different params */
125
126 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
127 runtime->dma_bytes = params_buffer_bytes(params);
128
129 prtd->params = rtd->cpu_dai->dma_data;
130 prtd->params->dma_intr_handler = at91rm9200_pcm_dma_irq;
131
132 prtd->dma_buffer = runtime->dma_addr;
133 prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
134 prtd->period_size = params_period_bytes(params);
135
136 DBG("hw_params: DMA for %s initialized (dma_bytes=%d, period_size=%d)\n",
137 prtd->params->name, runtime->dma_bytes, prtd->period_size);
138 return 0;
139}
140
141static int at91rm9200_pcm_hw_free(struct snd_pcm_substream *substream)
142{
143 struct at91rm9200_runtime_data *prtd = substream->runtime->private_data;
144 at91rm9200_pcm_dma_params_t *params = prtd->params;
145
146 if (params != NULL) {
147 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_disable);
148 prtd->params->dma_intr_handler = NULL;
149 }
150
151 return 0;
152}
153
154static int at91rm9200_pcm_prepare(struct snd_pcm_substream *substream)
155{
156 struct at91rm9200_runtime_data *prtd = substream->runtime->private_data;
157 at91rm9200_pcm_dma_params_t *params = prtd->params;
158
159 at91_ssc_write(params->ssc->idr,
160 params->mask->ssc_endx | params->mask->ssc_endbuf);
161
162 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_disable);
163 return 0;
164}
165
166static int at91rm9200_pcm_trigger(struct snd_pcm_substream *substream,
167 int cmd)
168{
169 struct at91rm9200_runtime_data *prtd = substream->runtime->private_data;
170 at91rm9200_pcm_dma_params_t *params = prtd->params;
171 int ret = 0;
172
173 switch (cmd) {
174 case SNDRV_PCM_TRIGGER_START:
175 prtd->period_ptr = prtd->dma_buffer;
176
177 at91_ssc_write(params->pdc->xpr, prtd->period_ptr);
178 at91_ssc_write(params->pdc->xcr,
179 prtd->period_size / params->pdc_xfer_size);
180
181 prtd->period_ptr += prtd->period_size;
182 at91_ssc_write(params->pdc->xnpr, prtd->period_ptr);
183 at91_ssc_write(params->pdc->xncr,
184 prtd->period_size / params->pdc_xfer_size);
185
186 DBG("trigger: period_ptr=%lx, xpr=%lx, xcr=%ld, xnpr=%lx, xncr=%ld\n",
187 (unsigned long) prtd->period_ptr,
188 at91_ssc_read(params->pdc->xpr),
189 at91_ssc_read(params->pdc->xcr),
190 at91_ssc_read(params->pdc->xnpr),
191 at91_ssc_read(params->pdc->xncr));
192
193 at91_ssc_write(params->ssc->ier,
194 params->mask->ssc_endx | params->mask->ssc_endbuf);
195
196 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_enable);
197
198 DBG("sr=%lx imr=%lx\n", at91_ssc_read(params->ssc->ier - 4),
199 at91_ssc_read(params->ssc->ier + 8));
200 break;
201
202 case SNDRV_PCM_TRIGGER_STOP:
203 case SNDRV_PCM_TRIGGER_SUSPEND:
204 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
205 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_disable);
206 break;
207
208 case SNDRV_PCM_TRIGGER_RESUME:
209 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
210 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_enable);
211 break;
212
213 default:
214 ret = -EINVAL;
215 }
216
217 return ret;
218}
219
220static snd_pcm_uframes_t at91rm9200_pcm_pointer(
221 struct snd_pcm_substream *substream)
222{
223 struct snd_pcm_runtime *runtime = substream->runtime;
224 struct at91rm9200_runtime_data *prtd = runtime->private_data;
225 at91rm9200_pcm_dma_params_t *params = prtd->params;
226 dma_addr_t ptr;
227 snd_pcm_uframes_t x;
228
229 ptr = (dma_addr_t) at91_ssc_read(params->pdc->xpr);
230 x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
231
232 if (x == runtime->buffer_size)
233 x = 0;
234 return x;
235}
236
237static int at91rm9200_pcm_open(struct snd_pcm_substream *substream)
238{
239 struct snd_pcm_runtime *runtime = substream->runtime;
240 struct at91rm9200_runtime_data *prtd;
241 int ret = 0;
242
243 snd_soc_set_runtime_hwparams(substream, &at91rm9200_pcm_hardware);
244
245 /* ensure that buffer size is a multiple of period size */
246 ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
247 if (ret < 0)
248 goto out;
249
250 prtd = kzalloc(sizeof(struct at91rm9200_runtime_data), GFP_KERNEL);
251 if (prtd == NULL) {
252 ret = -ENOMEM;
253 goto out;
254 }
255 runtime->private_data = prtd;
256
257 out:
258 return ret;
259}
260
261static int at91rm9200_pcm_close(struct snd_pcm_substream *substream)
262{
263 struct at91rm9200_runtime_data *prtd = substream->runtime->private_data;
264
265 kfree(prtd);
266 return 0;
267}
268
269static int at91rm9200_pcm_mmap(struct snd_pcm_substream *substream,
270 struct vm_area_struct *vma)
271{
272 struct snd_pcm_runtime *runtime = substream->runtime;
273
274 return dma_mmap_writecombine(substream->pcm->card->dev, vma,
275 runtime->dma_area,
276 runtime->dma_addr,
277 runtime->dma_bytes);
278}
279
280struct snd_pcm_ops at91rm9200_pcm_ops = {
281 .open = at91rm9200_pcm_open,
282 .close = at91rm9200_pcm_close,
283 .ioctl = snd_pcm_lib_ioctl,
284 .hw_params = at91rm9200_pcm_hw_params,
285 .hw_free = at91rm9200_pcm_hw_free,
286 .prepare = at91rm9200_pcm_prepare,
287 .trigger = at91rm9200_pcm_trigger,
288 .pointer = at91rm9200_pcm_pointer,
289 .mmap = at91rm9200_pcm_mmap,
290};
291
292static int at91rm9200_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
293 int stream)
294{
295 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
296 struct snd_dma_buffer *buf = &substream->dma_buffer;
297 size_t size = at91rm9200_pcm_hardware.buffer_bytes_max;
298
299 buf->dev.type = SNDRV_DMA_TYPE_DEV;
300 buf->dev.dev = pcm->card->dev;
301 buf->private_data = NULL;
302 buf->area = dma_alloc_writecombine(pcm->card->dev, size,
303 &buf->addr, GFP_KERNEL);
304
305 DBG("preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
306 (void *) buf->area,
307 (void *) buf->addr,
308 size);
309
310 if (!buf->area)
311 return -ENOMEM;
312
313 buf->bytes = size;
314 return 0;
315}
316
317static u64 at91rm9200_pcm_dmamask = 0xffffffff;
318
319static int at91rm9200_pcm_new(struct snd_card *card,
320 struct snd_soc_codec_dai *dai, struct snd_pcm *pcm)
321{
322 int ret = 0;
323
324 if (!card->dev->dma_mask)
325 card->dev->dma_mask = &at91rm9200_pcm_dmamask;
326 if (!card->dev->coherent_dma_mask)
327 card->dev->coherent_dma_mask = 0xffffffff;
328
329 if (dai->playback.channels_min) {
330 ret = at91rm9200_pcm_preallocate_dma_buffer(pcm,
331 SNDRV_PCM_STREAM_PLAYBACK);
332 if (ret)
333 goto out;
334 }
335
336 if (dai->capture.channels_min) {
337 ret = at91rm9200_pcm_preallocate_dma_buffer(pcm,
338 SNDRV_PCM_STREAM_CAPTURE);
339 if (ret)
340 goto out;
341 }
342 out:
343 return ret;
344}
345
346static void at91rm9200_pcm_free_dma_buffers(struct snd_pcm *pcm)
347{
348 struct snd_pcm_substream *substream;
349 struct snd_dma_buffer *buf;
350 int stream;
351
352 for (stream = 0; stream < 2; stream++) {
353 substream = pcm->streams[stream].substream;
354 if (!substream)
355 continue;
356
357 buf = &substream->dma_buffer;
358 if (!buf->area)
359 continue;
360
361 dma_free_writecombine(pcm->card->dev, buf->bytes,
362 buf->area, buf->addr);
363 buf->area = NULL;
364 }
365}
366
367static int at91rm9200_pcm_suspend(struct platform_device *pdev,
368 struct snd_soc_cpu_dai *dai)
369{
370 struct snd_pcm_runtime *runtime = dai->runtime;
371 struct at91rm9200_runtime_data *prtd;
372 at91rm9200_pcm_dma_params_t *params;
373
374 if (!runtime)
375 return 0;
376
377 prtd = runtime->private_data;
378 params = prtd->params;
379
380 /* disable the PDC and save the PDC registers */
381
382 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_disable);
383
384 prtd->pdc_xpr_save = at91_ssc_read(params->pdc->xpr);
385 prtd->pdc_xcr_save = at91_ssc_read(params->pdc->xcr);
386 prtd->pdc_xnpr_save = at91_ssc_read(params->pdc->xnpr);
387 prtd->pdc_xncr_save = at91_ssc_read(params->pdc->xncr);
388
389 return 0;
390}
391
392static int at91rm9200_pcm_resume(struct platform_device *pdev,
393 struct snd_soc_cpu_dai *dai)
394{
395 struct snd_pcm_runtime *runtime = dai->runtime;
396 struct at91rm9200_runtime_data *prtd;
397 at91rm9200_pcm_dma_params_t *params;
398
399 if (!runtime)
400 return 0;
401
402 prtd = runtime->private_data;
403 params = prtd->params;
404
405 /* restore the PDC registers and enable the PDC */
406 at91_ssc_write(params->pdc->xpr, prtd->pdc_xpr_save);
407 at91_ssc_write(params->pdc->xcr, prtd->pdc_xcr_save);
408 at91_ssc_write(params->pdc->xnpr, prtd->pdc_xnpr_save);
409 at91_ssc_write(params->pdc->xncr, prtd->pdc_xncr_save);
410
411 at91_ssc_write(params->pdc->ptcr, params->mask->pdc_enable);
412 return 0;
413}
414
415struct snd_soc_platform at91rm9200_soc_platform = {
416 .name = "at91rm9200-audio",
417 .pcm_ops = &at91rm9200_pcm_ops,
418 .pcm_new = at91rm9200_pcm_new,
419 .pcm_free = at91rm9200_pcm_free_dma_buffers,
420 .suspend = at91rm9200_pcm_suspend,
421 .resume = at91rm9200_pcm_resume,
422};
423
424EXPORT_SYMBOL_GPL(at91rm9200_soc_platform);
425
426MODULE_AUTHOR("Frank Mandarino <fmandarino@endrelia.com>");
427MODULE_DESCRIPTION("Atmel AT91RM9200 PCM module");
428MODULE_LICENSE("GPL");
diff --git a/sound/soc/at91/at91rm9200-pcm.h b/sound/soc/at91/at91rm9200-pcm.h
new file mode 100644
index 00000000000..65468f17377
--- /dev/null
+++ b/sound/soc/at91/at91rm9200-pcm.h
@@ -0,0 +1,75 @@
1/*
2 * at91rm9200-pcm.h - ALSA PCM interface for the Atmel AT91RM9200 chip
3 *
4 * Author: Frank Mandarino <fmandarino@endrelia.com>
5 * Endrelia Technologies Inc.
6 * Created: Mar 3, 2006
7 *
8 * Based on pxa2xx-pcm.h by:
9 *
10 * Author: Nicolas Pitre
11 * Created: Nov 30, 2004
12 * Copyright: MontaVista Software, Inc.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License version 2 as
16 * published by the Free Software Foundation.
17 */
18
19/*
20 * Registers and status bits that are required by the PCM driver.
21 */
22struct at91rm9200_ssc_regs {
23 void __iomem *cr; /* SSC control */
24 void __iomem *ier; /* SSC interrupt enable */
25 void __iomem *idr; /* SSC interrupt disable */
26};
27
28struct at91rm9200_pdc_regs {
29 void __iomem *xpr; /* PDC recv/trans pointer */
30 void __iomem *xcr; /* PDC recv/trans counter */
31 void __iomem *xnpr; /* PDC next recv/trans pointer */
32 void __iomem *xncr; /* PDC next recv/trans counter */
33 void __iomem *ptcr; /* PDC transfer control */
34};
35
36struct at91rm9200_ssc_mask {
37 u32 ssc_enable; /* SSC recv/trans enable */
38 u32 ssc_disable; /* SSC recv/trans disable */
39 u32 ssc_endx; /* SSC ENDTX or ENDRX */
40 u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
41 u32 pdc_enable; /* PDC recv/trans enable */
42 u32 pdc_disable; /* PDC recv/trans disable */
43};
44
45
46/*
47 * This structure, shared between the PCM driver and the interface,
48 * contains all information required by the PCM driver to perform the
49 * PDC DMA operation. All fields except dma_intr_handler() are initialized
50 * by the interface. The dms_intr_handler() pointer is set by the PCM
51 * driver and called by the interface SSC interrupt handler if it is
52 * non-NULL.
53 */
54typedef struct {
55 char *name; /* stream identifier */
56 int pdc_xfer_size; /* PDC counter increment in bytes */
57 struct at91rm9200_ssc_regs *ssc; /* SSC register addresses */
58 struct at91rm9200_pdc_regs *pdc; /* PDC receive/transmit registers */
59 struct at91rm9200_ssc_mask *mask;/* SSC & PDC status bits */
60 snd_pcm_substream_t *substream;
61 void (*dma_intr_handler)(u32, snd_pcm_substream_t *);
62} at91rm9200_pcm_dma_params_t;
63
64extern struct snd_soc_cpu_dai at91rm9200_i2s_dai[3];
65extern struct snd_soc_platform at91rm9200_soc_platform;
66
67
68/*
69 * SSC I/O helpers.
70 * E.g., at91_ssc_write(AT91_SSC(1) + AT91_SSC_CR, AT91_SSC_RXEN);
71 */
72#define AT91_SSC(x) (((x)==0) ? AT91_VA_BASE_SSC0 :\
73 ((x)==1) ? AT91_VA_BASE_SSC1 : ((x)==2) ? AT91_VA_BASE_SSC2 : NULL)
74#define at91_ssc_read(a) ((unsigned long) __raw_readl(a))
75#define at91_ssc_write(a,v) __raw_writel((v),(a))