ASoC: NUC900/audio: add nuc900 audio driver support
[pandora-kernel.git] / sound / soc / nuc900 / nuc900-pcm.c
1 /*
2  * Copyright (c) 2010 Nuvoton technology corporation.
3  *
4  * Wan ZongShun <mcuos.com@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation;version 2 of the License.
9  *
10  */
11
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/io.h>
15 #include <linux/platform_device.h>
16 #include <linux/slab.h>
17 #include <linux/dma-mapping.h>
18
19 #include <sound/core.h>
20 #include <sound/pcm.h>
21 #include <sound/pcm_params.h>
22 #include <sound/soc.h>
23
24 #include <mach/hardware.h>
25
26 #include "nuc900-auido.h"
27
28 static const struct snd_pcm_hardware nuc900_pcm_hardware = {
29         .info                   = SNDRV_PCM_INFO_INTERLEAVED |
30                                         SNDRV_PCM_INFO_BLOCK_TRANSFER |
31                                         SNDRV_PCM_INFO_MMAP |
32                                         SNDRV_PCM_INFO_MMAP_VALID |
33                                         SNDRV_PCM_INFO_PAUSE |
34                                         SNDRV_PCM_INFO_RESUME,
35         .formats                = SNDRV_PCM_FMTBIT_S16_LE,
36         .channels_min           = 1,
37         .channels_max           = 2,
38         .buffer_bytes_max       = 4*1024,
39         .period_bytes_min       = 1*1024,
40         .period_bytes_max       = 4*1024,
41         .periods_min            = 1,
42         .periods_max            = 1024,
43 };
44
45 static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
46         struct snd_pcm_hw_params *params)
47 {
48         struct snd_pcm_runtime *runtime = substream->runtime;
49         struct nuc900_audio *nuc900_audio = runtime->private_data;
50         unsigned long flags, stype = SUBSTREAM_TYPE(substream);
51         int ret = 0;
52
53         spin_lock_irqsave(&nuc900_audio->lock, flags);
54
55         ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
56         if (ret < 0)
57                 return ret;
58
59         nuc900_audio->substream = substream;
60         nuc900_audio->dma_addr[stype] = runtime->dma_addr;
61         nuc900_audio->buffersize[stype] = params_buffer_bytes(params);
62
63         spin_unlock_irqrestore(&nuc900_audio->lock, flags);
64
65         return ret;
66 }
67
68 static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
69                                 dma_addr_t dma_addr, size_t count)
70 {
71         struct snd_pcm_runtime *runtime = substream->runtime;
72         struct nuc900_audio *nuc900_audio = runtime->private_data;
73         void __iomem *mmio_addr, *mmio_len;
74
75         if (SUBSTREAM_TYPE(substream) == PCM_TX) {
76                 mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
77                 mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
78         } else {
79                 mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
80                 mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
81         }
82
83         AUDIO_WRITE(mmio_addr, dma_addr);
84         AUDIO_WRITE(mmio_len, count);
85 }
86
87 static void nuc900_dma_start(struct snd_pcm_substream *substream)
88 {
89         struct snd_pcm_runtime *runtime = substream->runtime;
90         struct nuc900_audio *nuc900_audio = runtime->private_data;
91         unsigned long val;
92
93         val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
94         val |= (T_DMA_IRQ | R_DMA_IRQ);
95         AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
96 }
97
98 static void nuc900_dma_stop(struct snd_pcm_substream *substream)
99 {
100         struct snd_pcm_runtime *runtime = substream->runtime;
101         struct nuc900_audio *nuc900_audio = runtime->private_data;
102         unsigned long val;
103
104         val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
105         val &= ~(T_DMA_IRQ | R_DMA_IRQ);
106         AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
107 }
108
109 static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
110 {
111         struct snd_pcm_substream *substream = dev_id;
112         struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
113         unsigned long val;
114
115         spin_lock(&nuc900_audio->lock);
116
117         val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
118
119         if (val & R_DMA_IRQ) {
120                 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
121
122                 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
123
124                 if (val & R_DMA_MIDDLE_IRQ) {
125                         val |= R_DMA_MIDDLE_IRQ;
126                         AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
127                 }
128
129                 if (val & R_DMA_END_IRQ) {
130                         val |= R_DMA_END_IRQ;
131                         AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
132                 }
133         } else if (val & T_DMA_IRQ) {
134                 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
135
136                 val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
137
138                 if (val & P_DMA_MIDDLE_IRQ) {
139                         val |= P_DMA_MIDDLE_IRQ;
140                         AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
141                 }
142
143                 if (val & P_DMA_END_IRQ) {
144                         val |= P_DMA_END_IRQ;
145                         AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
146                 }
147         } else {
148                 dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
149                 spin_unlock(&nuc900_audio->lock);
150                 return IRQ_HANDLED;
151         }
152
153         spin_unlock(&nuc900_audio->lock);
154
155         snd_pcm_period_elapsed(substream);
156
157         return IRQ_HANDLED;
158 }
159
160 static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
161 {
162         snd_pcm_lib_free_pages(substream);
163         return 0;
164 }
165
166 static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
167 {
168         struct snd_pcm_runtime *runtime = substream->runtime;
169         struct nuc900_audio *nuc900_audio = runtime->private_data;
170         unsigned long flags, val, stype = SUBSTREAM_TYPE(substream);;
171
172         spin_lock_irqsave(&nuc900_audio->lock, flags);
173
174         nuc900_update_dma_register(substream,
175                 nuc900_audio->dma_addr[stype], nuc900_audio->buffersize[stype]);
176
177         val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
178
179         switch (runtime->channels) {
180         case 1:
181                 if (PCM_TX == stype) {
182                         val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
183                         val |= PLAY_RIGHT_CHNNEL;
184                 } else {
185                         val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
186                         val |= RECORD_RIGHT_CHNNEL;
187                 }
188                 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
189                 break;
190         case 2:
191                 if (PCM_TX == stype)
192                         val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
193                 else
194                         val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
195                 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
196                 break;
197         default:
198                 return -EINVAL;
199         }
200         spin_unlock_irqrestore(&nuc900_audio->lock, flags);
201         return 0;
202 }
203
204 static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
205 {
206         int ret = 0;
207
208         switch (cmd) {
209         case SNDRV_PCM_TRIGGER_START:
210         case SNDRV_PCM_TRIGGER_RESUME:
211                 nuc900_dma_start(substream);
212                 break;
213
214         case SNDRV_PCM_TRIGGER_STOP:
215         case SNDRV_PCM_TRIGGER_SUSPEND:
216                 nuc900_dma_stop(substream);
217                 break;
218
219         default:
220                 ret = -EINVAL;
221                 break;
222         }
223
224         return ret;
225 }
226
227 int nuc900_dma_getposition(struct snd_pcm_substream *substream,
228                                         dma_addr_t *src, dma_addr_t *dst)
229 {
230         struct snd_pcm_runtime *runtime = substream->runtime;
231         struct nuc900_audio *nuc900_audio = runtime->private_data;
232
233         if (src != NULL)
234                 *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
235
236         if (dst != NULL)
237                 *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
238
239         return 0;
240 }
241
242 static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
243 {
244         struct snd_pcm_runtime *runtime = substream->runtime;
245         dma_addr_t src, dst;
246         unsigned long res;
247
248         nuc900_dma_getposition(substream, &src, &dst);
249
250         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
251                 res = dst - runtime->dma_addr;
252         else
253                 res = src - runtime->dma_addr;
254
255         return bytes_to_frames(substream->runtime, res);
256 }
257
258 static int nuc900_dma_open(struct snd_pcm_substream *substream)
259 {
260         struct snd_pcm_runtime *runtime = substream->runtime;
261         struct nuc900_audio *nuc900_audio;
262
263         snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
264
265         nuc900_audio = nuc900_ac97_data;
266
267         if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
268                         IRQF_DISABLED, "nuc900-dma", substream))
269                 return -EBUSY;
270
271         runtime->private_data = nuc900_audio;
272
273         return 0;
274 }
275
276 static int nuc900_dma_close(struct snd_pcm_substream *substream)
277 {
278         struct snd_pcm_runtime *runtime = substream->runtime;
279         struct nuc900_audio *nuc900_audio = runtime->private_data;
280
281         free_irq(nuc900_audio->irq_num, substream);
282
283         return 0;
284 }
285
286 static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
287         struct vm_area_struct *vma)
288 {
289         struct snd_pcm_runtime *runtime = substream->runtime;
290
291         return dma_mmap_writecombine(substream->pcm->card->dev, vma,
292                                         runtime->dma_area,
293                                         runtime->dma_addr,
294                                         runtime->dma_bytes);
295 }
296
297 static struct snd_pcm_ops nuc900_dma_ops = {
298         .open           = nuc900_dma_open,
299         .close          = nuc900_dma_close,
300         .ioctl          = snd_pcm_lib_ioctl,
301         .hw_params      = nuc900_dma_hw_params,
302         .hw_free        = nuc900_dma_hw_free,
303         .prepare        = nuc900_dma_prepare,
304         .trigger        = nuc900_dma_trigger,
305         .pointer        = nuc900_dma_pointer,
306         .mmap           = nuc900_dma_mmap,
307 };
308
309 static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
310 {
311         snd_pcm_lib_preallocate_free_for_all(pcm);
312 }
313
314 static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
315 static int nuc900_dma_new(struct snd_card *card,
316         struct snd_soc_dai *dai, struct snd_pcm *pcm)
317 {
318         if (!card->dev->dma_mask)
319                 card->dev->dma_mask = &nuc900_pcm_dmamask;
320         if (!card->dev->coherent_dma_mask)
321                 card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
322
323         snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
324                 card->dev, 4 * 1024, (4 * 1024) - 1);
325
326         return 0;
327 }
328
329 struct snd_soc_platform nuc900_soc_platform = {
330         .name           = "nuc900-dma",
331         .pcm_ops        = &nuc900_dma_ops,
332         .pcm_new        = nuc900_dma_new,
333         .pcm_free       = nuc900_dma_free_dma_buffers,
334 }
335 EXPORT_SYMBOL_GPL(nuc900_soc_platform);
336
337 static int __init nuc900_soc_platform_init(void)
338 {
339         return snd_soc_register_platform(&nuc900_soc_platform);
340 }
341
342 static void __exit nuc900_soc_platform_exit(void)
343 {
344         snd_soc_unregister_platform(&nuc900_soc_platform);
345 }
346
347 module_init(nuc900_soc_platform_init);
348 module_exit(nuc900_soc_platform_exit);
349
350 MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
351 MODULE_DESCRIPTION("nuc900 Audio DMA module");
352 MODULE_LICENSE("GPL");