2 * ALSA PCM interface for the Stetch s6000 family
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/platform_device.h>
15 #include <linux/slab.h>
16 #include <linux/dma-mapping.h>
17 #include <linux/interrupt.h>
19 #include <sound/core.h>
20 #include <sound/pcm.h>
21 #include <sound/pcm_params.h>
22 #include <sound/soc.h>
25 #include <variant/dmac.h>
27 #include "s6000-pcm.h"
29 #define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
30 #define S6_PCM_PREALLOCATE_MAX (2048 * 1024)
32 static struct snd_pcm_hardware s6000_pcm_hardware
= {
33 .info
= (SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
|
34 SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID
|
35 SNDRV_PCM_INFO_PAUSE
| SNDRV_PCM_INFO_JOINT_DUPLEX
),
36 .formats
= (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S32_LE
),
37 .rates
= (SNDRV_PCM_RATE_CONTINUOUS
| SNDRV_PCM_RATE_5512
| \
38 SNDRV_PCM_RATE_8000_192000
),
43 .buffer_bytes_max
= 0x7ffffff0,
44 .period_bytes_min
= 16,
45 .period_bytes_max
= 0xfffff0,
47 .periods_max
= 1024, /* no limit */
51 struct s6000_runtime_data
{
53 int period
; /* current DMA period */
56 static void s6000_pcm_enqueue_dma(struct snd_pcm_substream
*substream
)
58 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
59 struct s6000_runtime_data
*prtd
= runtime
->private_data
;
60 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
61 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
63 unsigned int period_size
;
64 unsigned int dma_offset
;
68 period_size
= snd_pcm_lib_period_bytes(substream
);
69 dma_offset
= prtd
->period
* period_size
;
70 dma_pos
= runtime
->dma_addr
+ dma_offset
;
72 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
75 channel
= par
->dma_out
;
79 channel
= par
->dma_in
;
82 if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel
),
83 DMA_INDEX_CHNL(channel
)))
86 if (s6dmac_fifo_full(DMA_MASK_DMAC(channel
), DMA_INDEX_CHNL(channel
))) {
87 printk(KERN_ERR
"s6000-pcm: fifo full\n");
91 BUG_ON(period_size
& 15);
92 s6dmac_put_fifo(DMA_MASK_DMAC(channel
), DMA_INDEX_CHNL(channel
),
93 src
, dst
, period_size
);
96 if (unlikely(prtd
->period
>= runtime
->periods
))
100 static irqreturn_t
s6000_pcm_irq(int irq
, void *data
)
102 struct snd_pcm
*pcm
= data
;
103 struct snd_soc_pcm_runtime
*runtime
= pcm
->private_data
;
104 struct s6000_pcm_dma_params
*params
= runtime
->dai
->cpu_dai
->dma_data
;
105 struct s6000_runtime_data
*prtd
;
106 unsigned int has_xrun
;
107 int i
, ret
= IRQ_NONE
;
109 [SNDRV_PCM_STREAM_PLAYBACK
] = params
->dma_out
,
110 [SNDRV_PCM_STREAM_CAPTURE
] = params
->dma_in
113 has_xrun
= params
->check_xrun(runtime
->dai
->cpu_dai
);
115 for (i
= 0; i
< ARRAY_SIZE(channel
); ++i
) {
116 struct snd_pcm_substream
*substream
= pcm
->streams
[i
].substream
;
117 unsigned int pending
;
122 if (unlikely(has_xrun
& (1 << i
)) &&
123 substream
->runtime
&&
124 snd_pcm_running(substream
)) {
125 dev_dbg(pcm
->dev
, "xrun\n");
126 snd_pcm_stop(substream
, SNDRV_PCM_STATE_XRUN
);
130 pending
= s6dmac_int_sources(DMA_MASK_DMAC(channel
[i
]),
131 DMA_INDEX_CHNL(channel
[i
]));
135 if (likely(substream
->runtime
&&
136 snd_pcm_running(substream
))) {
137 snd_pcm_period_elapsed(substream
);
138 dev_dbg(pcm
->dev
, "period elapsed %x %x\n",
139 s6dmac_cur_src(DMA_MASK_DMAC(channel
[i
]),
140 DMA_INDEX_CHNL(channel
[i
])),
141 s6dmac_cur_dst(DMA_MASK_DMAC(channel
[i
]),
142 DMA_INDEX_CHNL(channel
[i
])));
143 prtd
= substream
->runtime
->private_data
;
144 spin_lock(&prtd
->lock
);
145 s6000_pcm_enqueue_dma(substream
);
146 spin_unlock(&prtd
->lock
);
150 if (unlikely(pending
& ~7)) {
151 if (pending
& (1 << 3))
153 "s6000-pcm: DMA %x Underflow\n",
155 if (pending
& (1 << 4))
157 "s6000-pcm: DMA %x Overflow\n",
161 "s6000-pcm: DMA %x Master Error "
163 channel
[i
], pending
>> 5);
171 static int s6000_pcm_start(struct snd_pcm_substream
*substream
)
173 struct s6000_runtime_data
*prtd
= substream
->runtime
->private_data
;
174 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
175 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
180 spin_lock_irqsave(&prtd
->lock
, flags
);
182 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
189 s6dmac_enable_chan(DMA_MASK_DMAC(dma
), DMA_INDEX_CHNL(dma
),
190 1 /* priority 1 (0 is max) */,
191 0 /* peripheral requests w/o xfer length mode */,
192 srcinc
/* source address increment */,
193 srcinc
^1 /* destination address increment */,
194 0 /* chunksize 0 (skip impossible on this dma) */,
195 0 /* source skip after chunk (impossible) */,
196 0 /* destination skip after chunk (impossible) */,
197 4 /* 16 byte burst size */,
198 -1 /* don't conserve bandwidth */,
199 0 /* low watermark irq descriptor theshold */,
200 0 /* disable hardware timestamps */,
201 1 /* enable channel */);
203 s6000_pcm_enqueue_dma(substream
);
204 s6000_pcm_enqueue_dma(substream
);
206 spin_unlock_irqrestore(&prtd
->lock
, flags
);
211 static int s6000_pcm_stop(struct snd_pcm_substream
*substream
)
213 struct s6000_runtime_data
*prtd
= substream
->runtime
->private_data
;
214 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
215 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
219 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
220 channel
= par
->dma_out
;
222 channel
= par
->dma_in
;
224 s6dmac_set_terminal_count(DMA_MASK_DMAC(channel
),
225 DMA_INDEX_CHNL(channel
), 0);
227 spin_lock_irqsave(&prtd
->lock
, flags
);
229 s6dmac_disable_chan(DMA_MASK_DMAC(channel
), DMA_INDEX_CHNL(channel
));
231 spin_unlock_irqrestore(&prtd
->lock
, flags
);
236 static int s6000_pcm_trigger(struct snd_pcm_substream
*substream
, int cmd
)
238 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
239 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
242 ret
= par
->trigger(substream
, cmd
, 0);
247 case SNDRV_PCM_TRIGGER_START
:
248 case SNDRV_PCM_TRIGGER_RESUME
:
249 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
250 ret
= s6000_pcm_start(substream
);
252 case SNDRV_PCM_TRIGGER_STOP
:
253 case SNDRV_PCM_TRIGGER_SUSPEND
:
254 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
255 ret
= s6000_pcm_stop(substream
);
263 return par
->trigger(substream
, cmd
, 1);
266 static int s6000_pcm_prepare(struct snd_pcm_substream
*substream
)
268 struct s6000_runtime_data
*prtd
= substream
->runtime
->private_data
;
275 static snd_pcm_uframes_t
s6000_pcm_pointer(struct snd_pcm_substream
*substream
)
277 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
278 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
279 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
280 struct s6000_runtime_data
*prtd
= runtime
->private_data
;
285 spin_lock_irqsave(&prtd
->lock
, flags
);
287 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
288 count
= s6dmac_cur_src(DMA_MASK_DMAC(par
->dma_out
),
289 DMA_INDEX_CHNL(par
->dma_out
));
291 count
= s6dmac_cur_dst(DMA_MASK_DMAC(par
->dma_in
),
292 DMA_INDEX_CHNL(par
->dma_in
));
294 count
-= runtime
->dma_addr
;
296 spin_unlock_irqrestore(&prtd
->lock
, flags
);
298 offset
= bytes_to_frames(runtime
, count
);
299 if (unlikely(offset
>= runtime
->buffer_size
))
305 static int s6000_pcm_open(struct snd_pcm_substream
*substream
)
307 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
308 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
309 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
310 struct s6000_runtime_data
*prtd
;
313 snd_soc_set_runtime_hwparams(substream
, &s6000_pcm_hardware
);
315 ret
= snd_pcm_hw_constraint_step(runtime
, 0,
316 SNDRV_PCM_HW_PARAM_PERIOD_BYTES
, 16);
319 ret
= snd_pcm_hw_constraint_step(runtime
, 0,
320 SNDRV_PCM_HW_PARAM_BUFFER_BYTES
, 16);
323 ret
= snd_pcm_hw_constraint_integer(runtime
,
324 SNDRV_PCM_HW_PARAM_PERIODS
);
328 if (par
->same_rate
) {
330 spin_lock(&par
->lock
); /* needed? */
332 spin_unlock(&par
->lock
);
334 ret
= snd_pcm_hw_constraint_minmax(runtime
,
335 SNDRV_PCM_HW_PARAM_RATE
,
342 prtd
= kzalloc(sizeof(struct s6000_runtime_data
), GFP_KERNEL
);
346 spin_lock_init(&prtd
->lock
);
348 runtime
->private_data
= prtd
;
353 static int s6000_pcm_close(struct snd_pcm_substream
*substream
)
355 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
356 struct s6000_runtime_data
*prtd
= runtime
->private_data
;
363 static int s6000_pcm_hw_params(struct snd_pcm_substream
*substream
,
364 struct snd_pcm_hw_params
*hw_params
)
366 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
367 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
369 ret
= snd_pcm_lib_malloc_pages(substream
,
370 params_buffer_bytes(hw_params
));
372 printk(KERN_WARNING
"s6000-pcm: allocation of memory failed\n");
376 if (par
->same_rate
) {
377 spin_lock(&par
->lock
);
378 if (par
->rate
== -1 ||
379 !(par
->in_use
& ~(1 << substream
->stream
))) {
380 par
->rate
= params_rate(hw_params
);
381 par
->in_use
|= 1 << substream
->stream
;
382 } else if (params_rate(hw_params
) != par
->rate
) {
383 snd_pcm_lib_free_pages(substream
);
384 par
->in_use
&= ~(1 << substream
->stream
);
387 spin_unlock(&par
->lock
);
392 static int s6000_pcm_hw_free(struct snd_pcm_substream
*substream
)
394 struct snd_soc_pcm_runtime
*soc_runtime
= substream
->private_data
;
395 struct s6000_pcm_dma_params
*par
= soc_runtime
->dai
->cpu_dai
->dma_data
;
397 spin_lock(&par
->lock
);
398 par
->in_use
&= ~(1 << substream
->stream
);
401 spin_unlock(&par
->lock
);
403 return snd_pcm_lib_free_pages(substream
);
406 static struct snd_pcm_ops s6000_pcm_ops
= {
407 .open
= s6000_pcm_open
,
408 .close
= s6000_pcm_close
,
409 .ioctl
= snd_pcm_lib_ioctl
,
410 .hw_params
= s6000_pcm_hw_params
,
411 .hw_free
= s6000_pcm_hw_free
,
412 .trigger
= s6000_pcm_trigger
,
413 .prepare
= s6000_pcm_prepare
,
414 .pointer
= s6000_pcm_pointer
,
417 static void s6000_pcm_free(struct snd_pcm
*pcm
)
419 struct snd_soc_pcm_runtime
*runtime
= pcm
->private_data
;
420 struct s6000_pcm_dma_params
*params
= runtime
->dai
->cpu_dai
->dma_data
;
422 free_irq(params
->irq
, pcm
);
423 snd_pcm_lib_preallocate_free_for_all(pcm
);
426 static u64 s6000_pcm_dmamask
= DMA_32BIT_MASK
;
428 static int s6000_pcm_new(struct snd_card
*card
,
429 struct snd_soc_dai
*dai
, struct snd_pcm
*pcm
)
431 struct snd_soc_pcm_runtime
*runtime
= pcm
->private_data
;
432 struct s6000_pcm_dma_params
*params
= runtime
->dai
->cpu_dai
->dma_data
;
435 if (!card
->dev
->dma_mask
)
436 card
->dev
->dma_mask
= &s6000_pcm_dmamask
;
437 if (!card
->dev
->coherent_dma_mask
)
438 card
->dev
->coherent_dma_mask
= DMA_32BIT_MASK
;
440 if (params
->dma_in
) {
441 s6dmac_disable_chan(DMA_MASK_DMAC(params
->dma_in
),
442 DMA_INDEX_CHNL(params
->dma_in
));
443 s6dmac_int_sources(DMA_MASK_DMAC(params
->dma_in
),
444 DMA_INDEX_CHNL(params
->dma_in
));
447 if (params
->dma_out
) {
448 s6dmac_disable_chan(DMA_MASK_DMAC(params
->dma_out
),
449 DMA_INDEX_CHNL(params
->dma_out
));
450 s6dmac_int_sources(DMA_MASK_DMAC(params
->dma_out
),
451 DMA_INDEX_CHNL(params
->dma_out
));
454 res
= request_irq(params
->irq
, s6000_pcm_irq
, IRQF_SHARED
,
455 s6000_soc_platform
.name
, pcm
);
457 printk(KERN_ERR
"s6000-pcm couldn't get IRQ\n");
461 res
= snd_pcm_lib_preallocate_pages_for_all(pcm
,
464 S6_PCM_PREALLOCATE_SIZE
,
465 S6_PCM_PREALLOCATE_MAX
);
467 printk(KERN_WARNING
"s6000-pcm: preallocation failed\n");
469 spin_lock_init(¶ms
->lock
);
475 struct snd_soc_platform s6000_soc_platform
= {
476 .name
= "s6000-audio",
477 .pcm_ops
= &s6000_pcm_ops
,
478 .pcm_new
= s6000_pcm_new
,
479 .pcm_free
= s6000_pcm_free
,
481 EXPORT_SYMBOL_GPL(s6000_soc_platform
);
483 static int __init
s6000_pcm_init(void)
485 return snd_soc_register_platform(&s6000_soc_platform
);
487 module_init(s6000_pcm_init
);
489 static void __exit
s6000_pcm_exit(void)
491 snd_soc_unregister_platform(&s6000_soc_platform
);
493 module_exit(s6000_pcm_exit
);
495 MODULE_AUTHOR("Daniel Gloeckner");
496 MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
497 MODULE_LICENSE("GPL");