4 * (c) 2010 Arnaud Patard <apatard@mandriva.com>
5 * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version.
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/device.h>
17 #include <linux/slab.h>
18 #include <linux/interrupt.h>
19 #include <linux/dma-mapping.h>
20 #include <linux/mbus.h>
21 #include <sound/soc.h>
24 static struct kirkwood_dma_data
*kirkwood_priv(struct snd_pcm_substream
*subs
)
26 struct snd_soc_pcm_runtime
*soc_runtime
= subs
->private_data
;
27 return snd_soc_dai_get_drvdata(soc_runtime
->cpu_dai
);
30 static const struct snd_pcm_hardware kirkwood_dma_snd_hw
= {
31 .info
= SNDRV_PCM_INFO_INTERLEAVED
|
33 SNDRV_PCM_INFO_MMAP_VALID
|
34 SNDRV_PCM_INFO_BLOCK_TRANSFER
|
35 SNDRV_PCM_INFO_PAUSE
|
36 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP
,
37 .buffer_bytes_max
= KIRKWOOD_SND_MAX_BUFFER_BYTES
,
38 .period_bytes_min
= KIRKWOOD_SND_MIN_PERIOD_BYTES
,
39 .period_bytes_max
= KIRKWOOD_SND_MAX_PERIOD_BYTES
,
40 .periods_min
= KIRKWOOD_SND_MIN_PERIODS
,
41 .periods_max
= KIRKWOOD_SND_MAX_PERIODS
,
45 static irqreturn_t
kirkwood_dma_irq(int irq
, void *dev_id
)
47 struct kirkwood_dma_data
*priv
= dev_id
;
48 unsigned long mask
, status
, cause
;
50 mask
= readl(priv
->io
+ KIRKWOOD_INT_MASK
);
51 status
= readl(priv
->io
+ KIRKWOOD_INT_CAUSE
) & mask
;
53 cause
= readl(priv
->io
+ KIRKWOOD_ERR_CAUSE
);
54 if (unlikely(cause
)) {
55 printk(KERN_WARNING
"%s: got err interrupt 0x%lx\n",
57 writel(cause
, priv
->io
+ KIRKWOOD_ERR_CAUSE
);
60 /* we've enabled only bytes interrupts ... */
61 if (status
& ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES
| \
62 KIRKWOOD_INT_CAUSE_REC_BYTES
)) {
63 printk(KERN_WARNING
"%s: unexpected interrupt %lx\n",
69 writel(status
, priv
->io
+ KIRKWOOD_INT_CAUSE
);
71 if (status
& KIRKWOOD_INT_CAUSE_PLAY_BYTES
)
72 snd_pcm_period_elapsed(priv
->substream_play
);
74 if (status
& KIRKWOOD_INT_CAUSE_REC_BYTES
)
75 snd_pcm_period_elapsed(priv
->substream_rec
);
81 kirkwood_dma_conf_mbus_windows(void __iomem
*base
, int win
,
83 const struct mbus_dram_target_info
*dram
)
87 /* First disable and clear windows */
88 writel(0, base
+ KIRKWOOD_AUDIO_WIN_CTRL_REG(win
));
89 writel(0, base
+ KIRKWOOD_AUDIO_WIN_BASE_REG(win
));
91 /* try to find matching cs for current dma address */
92 for (i
= 0; i
< dram
->num_cs
; i
++) {
93 const struct mbus_dram_window
*cs
= dram
->cs
+ i
;
94 if ((cs
->base
& 0xffff0000) < (dma
& 0xffff0000)) {
95 writel(cs
->base
& 0xffff0000,
96 base
+ KIRKWOOD_AUDIO_WIN_BASE_REG(win
));
97 writel(((cs
->size
- 1) & 0xffff0000) |
98 (cs
->mbus_attr
<< 8) |
99 (dram
->mbus_dram_target_id
<< 4) | 1,
100 base
+ KIRKWOOD_AUDIO_WIN_CTRL_REG(win
));
105 static int kirkwood_dma_open(struct snd_pcm_substream
*substream
)
108 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
109 struct kirkwood_dma_data
*priv
= kirkwood_priv(substream
);
110 const struct mbus_dram_target_info
*dram
;
113 snd_soc_set_runtime_hwparams(substream
, &kirkwood_dma_snd_hw
);
115 /* Ensure that all constraints linked to dma burst are fulfilled */
116 err
= snd_pcm_hw_constraint_minmax(runtime
,
117 SNDRV_PCM_HW_PARAM_BUFFER_BYTES
,
119 KIRKWOOD_AUDIO_BUF_MAX
-1);
123 err
= snd_pcm_hw_constraint_step(runtime
, 0,
124 SNDRV_PCM_HW_PARAM_BUFFER_BYTES
,
129 err
= snd_pcm_hw_constraint_step(substream
->runtime
, 0,
130 SNDRV_PCM_HW_PARAM_PERIOD_BYTES
,
135 if (!priv
->substream_play
&& !priv
->substream_rec
) {
136 err
= request_irq(priv
->irq
, kirkwood_dma_irq
, IRQF_SHARED
,
137 "kirkwood-i2s", priv
);
142 * Enable Error interrupts. We're only ack'ing them but
143 * it's useful for diagnostics
145 writel((unsigned int)-1, priv
->io
+ KIRKWOOD_ERR_MASK
);
148 dram
= mv_mbus_dram_info();
149 addr
= substream
->dma_buffer
.addr
;
150 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
151 if (priv
->substream_play
)
153 priv
->substream_play
= substream
;
154 kirkwood_dma_conf_mbus_windows(priv
->io
,
155 KIRKWOOD_PLAYBACK_WIN
, addr
, dram
);
157 if (priv
->substream_rec
)
159 priv
->substream_rec
= substream
;
160 kirkwood_dma_conf_mbus_windows(priv
->io
,
161 KIRKWOOD_RECORD_WIN
, addr
, dram
);
167 static int kirkwood_dma_close(struct snd_pcm_substream
*substream
)
169 struct kirkwood_dma_data
*priv
= kirkwood_priv(substream
);
174 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
175 priv
->substream_play
= NULL
;
177 priv
->substream_rec
= NULL
;
179 if (!priv
->substream_play
&& !priv
->substream_rec
) {
180 writel(0, priv
->io
+ KIRKWOOD_ERR_MASK
);
181 free_irq(priv
->irq
, priv
);
187 static int kirkwood_dma_hw_params(struct snd_pcm_substream
*substream
,
188 struct snd_pcm_hw_params
*params
)
190 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
192 snd_pcm_set_runtime_buffer(substream
, &substream
->dma_buffer
);
193 runtime
->dma_bytes
= params_buffer_bytes(params
);
198 static int kirkwood_dma_hw_free(struct snd_pcm_substream
*substream
)
200 snd_pcm_set_runtime_buffer(substream
, NULL
);
204 static int kirkwood_dma_prepare(struct snd_pcm_substream
*substream
)
206 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
207 struct kirkwood_dma_data
*priv
= kirkwood_priv(substream
);
208 unsigned long size
, count
;
210 /* compute buffer size in term of "words" as requested in specs */
211 size
= frames_to_bytes(runtime
, runtime
->buffer_size
);
213 count
= snd_pcm_lib_period_bytes(substream
);
215 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
216 writel(count
, priv
->io
+ KIRKWOOD_PLAY_BYTE_INT_COUNT
);
217 writel(runtime
->dma_addr
, priv
->io
+ KIRKWOOD_PLAY_BUF_ADDR
);
218 writel(size
, priv
->io
+ KIRKWOOD_PLAY_BUF_SIZE
);
220 writel(count
, priv
->io
+ KIRKWOOD_REC_BYTE_INT_COUNT
);
221 writel(runtime
->dma_addr
, priv
->io
+ KIRKWOOD_REC_BUF_ADDR
);
222 writel(size
, priv
->io
+ KIRKWOOD_REC_BUF_SIZE
);
229 static snd_pcm_uframes_t
kirkwood_dma_pointer(struct snd_pcm_substream
232 struct kirkwood_dma_data
*priv
= kirkwood_priv(substream
);
233 snd_pcm_uframes_t count
;
235 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
236 count
= bytes_to_frames(substream
->runtime
,
237 readl(priv
->io
+ KIRKWOOD_PLAY_BYTE_COUNT
));
239 count
= bytes_to_frames(substream
->runtime
,
240 readl(priv
->io
+ KIRKWOOD_REC_BYTE_COUNT
));
245 static const struct snd_pcm_ops kirkwood_dma_ops
= {
246 .open
= kirkwood_dma_open
,
247 .close
= kirkwood_dma_close
,
248 .ioctl
= snd_pcm_lib_ioctl
,
249 .hw_params
= kirkwood_dma_hw_params
,
250 .hw_free
= kirkwood_dma_hw_free
,
251 .prepare
= kirkwood_dma_prepare
,
252 .pointer
= kirkwood_dma_pointer
,
255 static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm
*pcm
,
258 struct snd_pcm_substream
*substream
= pcm
->streams
[stream
].substream
;
259 struct snd_dma_buffer
*buf
= &substream
->dma_buffer
;
260 size_t size
= kirkwood_dma_snd_hw
.buffer_bytes_max
;
262 buf
->dev
.type
= SNDRV_DMA_TYPE_DEV
;
263 buf
->dev
.dev
= pcm
->card
->dev
;
264 buf
->area
= dma_alloc_coherent(pcm
->card
->dev
, size
,
265 &buf
->addr
, GFP_KERNEL
);
269 buf
->private_data
= NULL
;
274 static int kirkwood_dma_new(struct snd_soc_pcm_runtime
*rtd
)
276 struct snd_card
*card
= rtd
->card
->snd_card
;
277 struct snd_pcm
*pcm
= rtd
->pcm
;
280 ret
= dma_coerce_mask_and_coherent(card
->dev
, DMA_BIT_MASK(32));
284 if (pcm
->streams
[SNDRV_PCM_STREAM_PLAYBACK
].substream
) {
285 ret
= kirkwood_dma_preallocate_dma_buffer(pcm
,
286 SNDRV_PCM_STREAM_PLAYBACK
);
291 if (pcm
->streams
[SNDRV_PCM_STREAM_CAPTURE
].substream
) {
292 ret
= kirkwood_dma_preallocate_dma_buffer(pcm
,
293 SNDRV_PCM_STREAM_CAPTURE
);
301 static void kirkwood_dma_free_dma_buffers(struct snd_pcm
*pcm
)
303 struct snd_pcm_substream
*substream
;
304 struct snd_dma_buffer
*buf
;
307 for (stream
= 0; stream
< 2; stream
++) {
308 substream
= pcm
->streams
[stream
].substream
;
311 buf
= &substream
->dma_buffer
;
315 dma_free_coherent(pcm
->card
->dev
, buf
->bytes
,
316 buf
->area
, buf
->addr
);
321 const struct snd_soc_platform_driver kirkwood_soc_platform
= {
322 .ops
= &kirkwood_dma_ops
,
323 .pcm_new
= kirkwood_dma_new
,
324 .pcm_free
= kirkwood_dma_free_dma_buffers
,