]> git.karo-electronics.de Git - mv-sheeva.git/blob - sound/soc/jz4740/jz4740-pcm.c
ASoC: multi-component - ASoC Multi-Component Support
[mv-sheeva.git] / sound / soc / jz4740 / jz4740-pcm.c
1 /*
2  *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
3  *
4  *  This program is free software; you can redistribute  it and/or modify it
5  *  under  the terms of  the GNU General  Public License as published by the
6  *  Free Software Foundation;  either version 2 of the  License, or (at your
7  *  option) any later version.
8  *
9  *  You should have received a copy of the  GNU General Public License along
10  *  with this program; if not, write  to the Free Software Foundation, Inc.,
11  *  675 Mass Ave, Cambridge, MA 02139, USA.
12  *
13  */
14
15 #include <linux/init.h>
16 #include <linux/interrupt.h>
17 #include <linux/kernel.h>
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/slab.h>
21
22 #include <linux/dma-mapping.h>
23
24 #include <sound/core.h>
25 #include <sound/pcm.h>
26 #include <sound/pcm_params.h>
27 #include <sound/soc.h>
28
29 #include <asm/mach-jz4740/dma.h>
30 #include "jz4740-pcm.h"
31
32 struct jz4740_runtime_data {
33         unsigned long dma_period;
34         dma_addr_t dma_start;
35         dma_addr_t dma_pos;
36         dma_addr_t dma_end;
37
38         struct jz4740_dma_chan *dma;
39
40         dma_addr_t fifo_addr;
41 };
42
43 /* identify hardware playback capabilities */
44 static const struct snd_pcm_hardware jz4740_pcm_hardware = {
45         .info = SNDRV_PCM_INFO_MMAP |
46                 SNDRV_PCM_INFO_MMAP_VALID |
47                 SNDRV_PCM_INFO_INTERLEAVED |
48                 SNDRV_PCM_INFO_BLOCK_TRANSFER,
49         .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
50
51         .rates                  = SNDRV_PCM_RATE_8000_48000,
52         .channels_min           = 1,
53         .channels_max           = 2,
54         .period_bytes_min       = 16,
55         .period_bytes_max       = 2 * PAGE_SIZE,
56         .periods_min            = 2,
57         .periods_max            = 128,
58         .buffer_bytes_max       = 128 * 2 * PAGE_SIZE,
59         .fifo_size              = 32,
60 };
61
62 static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
63         struct snd_pcm_substream *substream)
64 {
65         unsigned long count;
66
67         if (prtd->dma_pos == prtd->dma_end)
68                 prtd->dma_pos = prtd->dma_start;
69
70         if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
71                 count = prtd->dma_end - prtd->dma_pos;
72         else
73                 count = prtd->dma_period;
74
75         jz4740_dma_disable(prtd->dma);
76
77         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
78                 jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
79                 jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
80         } else {
81                 jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
82                 jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
83         }
84
85         jz4740_dma_set_transfer_count(prtd->dma, count);
86
87         prtd->dma_pos += count;
88
89         jz4740_dma_enable(prtd->dma);
90 }
91
92 static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
93         void *dev_id)
94 {
95         struct snd_pcm_substream *substream = dev_id;
96         struct snd_pcm_runtime *runtime = substream->runtime;
97         struct jz4740_runtime_data *prtd = runtime->private_data;
98
99         snd_pcm_period_elapsed(substream);
100
101         jz4740_pcm_start_transfer(prtd, substream);
102 }
103
104 static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
105         struct snd_pcm_hw_params *params)
106 {
107         struct snd_pcm_runtime *runtime = substream->runtime;
108         struct jz4740_runtime_data *prtd = runtime->private_data;
109         struct snd_soc_pcm_runtime *rtd = substream->private_data;
110         struct jz4740_pcm_config *config;
111
112         config = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
113
114         if (!config)
115                 return 0;
116
117         if (!prtd->dma) {
118                 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
119                         prtd->dma = jz4740_dma_request(substream, "PCM Capture");
120                 else
121                         prtd->dma = jz4740_dma_request(substream, "PCM Playback");
122         }
123
124         if (!prtd->dma)
125                 return -EBUSY;
126
127         jz4740_dma_configure(prtd->dma, &config->dma_config);
128         prtd->fifo_addr = config->fifo_addr;
129
130         jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
131
132         snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
133         runtime->dma_bytes = params_buffer_bytes(params);
134
135         prtd->dma_period = params_period_bytes(params);
136         prtd->dma_start = runtime->dma_addr;
137         prtd->dma_pos = prtd->dma_start;
138         prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
139
140         return 0;
141 }
142
143 static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
144 {
145         struct jz4740_runtime_data *prtd = substream->runtime->private_data;
146
147         snd_pcm_set_runtime_buffer(substream, NULL);
148         if (prtd->dma) {
149                 jz4740_dma_free(prtd->dma);
150                 prtd->dma = NULL;
151         }
152
153         return 0;
154 }
155
156 static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
157 {
158         struct jz4740_runtime_data *prtd = substream->runtime->private_data;
159
160         if (!prtd->dma)
161                 return -EBUSY;
162
163         prtd->dma_pos = prtd->dma_start;
164
165         return 0;
166 }
167
168 static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
169 {
170         struct snd_pcm_runtime *runtime = substream->runtime;
171         struct jz4740_runtime_data *prtd = runtime->private_data;
172
173         switch (cmd) {
174         case SNDRV_PCM_TRIGGER_START:
175         case SNDRV_PCM_TRIGGER_RESUME:
176         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
177                 jz4740_pcm_start_transfer(prtd, substream);
178                 break;
179         case SNDRV_PCM_TRIGGER_STOP:
180         case SNDRV_PCM_TRIGGER_SUSPEND:
181         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
182                 jz4740_dma_disable(prtd->dma);
183                 break;
184         default:
185                 break;
186         }
187
188         return 0;
189 }
190
191 static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
192 {
193         struct snd_pcm_runtime *runtime = substream->runtime;
194         struct jz4740_runtime_data *prtd = runtime->private_data;
195         unsigned long byte_offset;
196         snd_pcm_uframes_t offset;
197         struct jz4740_dma_chan *dma = prtd->dma;
198
199         /* prtd->dma_pos points to the end of the current transfer. So by
200          * subtracting prdt->dma_start we get the offset to the end of the
201          * current period in bytes. By subtracting the residue of the transfer
202          * we get the current offset in bytes. */
203         byte_offset = prtd->dma_pos - prtd->dma_start;
204         byte_offset -= jz4740_dma_get_residue(dma);
205
206         offset = bytes_to_frames(runtime, byte_offset);
207         if (offset >= runtime->buffer_size)
208                 offset = 0;
209
210         return offset;
211 }
212
213 static int jz4740_pcm_open(struct snd_pcm_substream *substream)
214 {
215         struct snd_pcm_runtime *runtime = substream->runtime;
216         struct jz4740_runtime_data *prtd;
217
218         prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
219         if (prtd == NULL)
220                 return -ENOMEM;
221
222         snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
223
224         runtime->private_data = prtd;
225
226         return 0;
227 }
228
229 static int jz4740_pcm_close(struct snd_pcm_substream *substream)
230 {
231         struct snd_pcm_runtime *runtime = substream->runtime;
232         struct jz4740_runtime_data *prtd = runtime->private_data;
233
234         kfree(prtd);
235
236         return 0;
237 }
238
239 static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
240         struct vm_area_struct *vma)
241 {
242         return remap_pfn_range(vma, vma->vm_start,
243                         substream->dma_buffer.addr >> PAGE_SHIFT,
244                         vma->vm_end - vma->vm_start, vma->vm_page_prot);
245 }
246
247 static struct snd_pcm_ops jz4740_pcm_ops = {
248         .open           = jz4740_pcm_open,
249         .close          = jz4740_pcm_close,
250         .ioctl          = snd_pcm_lib_ioctl,
251         .hw_params      = jz4740_pcm_hw_params,
252         .hw_free        = jz4740_pcm_hw_free,
253         .prepare        = jz4740_pcm_prepare,
254         .trigger        = jz4740_pcm_trigger,
255         .pointer        = jz4740_pcm_pointer,
256         .mmap           = jz4740_pcm_mmap,
257 };
258
259 static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
260 {
261         struct snd_pcm_substream *substream = pcm->streams[stream].substream;
262         struct snd_dma_buffer *buf = &substream->dma_buffer;
263         size_t size = jz4740_pcm_hardware.buffer_bytes_max;
264
265         buf->dev.type = SNDRV_DMA_TYPE_DEV;
266         buf->dev.dev = pcm->card->dev;
267         buf->private_data = NULL;
268
269         buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
270                                           &buf->addr, GFP_KERNEL);
271         if (!buf->area)
272                 return -ENOMEM;
273
274         buf->bytes = size;
275
276         return 0;
277 }
278
279 static void jz4740_pcm_free(struct snd_pcm *pcm)
280 {
281         struct snd_pcm_substream *substream;
282         struct snd_dma_buffer *buf;
283         int stream;
284
285         for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
286                 substream = pcm->streams[stream].substream;
287                 if (!substream)
288                         continue;
289
290                 buf = &substream->dma_buffer;
291                 if (!buf->area)
292                         continue;
293
294                 dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
295                                 buf->addr);
296                 buf->area = NULL;
297         }
298 }
299
300 static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
301
302 int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
303         struct snd_pcm *pcm)
304 {
305         int ret = 0;
306
307         if (!card->dev->dma_mask)
308                 card->dev->dma_mask = &jz4740_pcm_dmamask;
309
310         if (!card->dev->coherent_dma_mask)
311                 card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
312
313         if (dai->driver->playback.channels_min) {
314                 ret = jz4740_pcm_preallocate_dma_buffer(pcm,
315                         SNDRV_PCM_STREAM_PLAYBACK);
316                 if (ret)
317                         goto err;
318         }
319
320         if (dai->driver->capture.channels_min) {
321                 ret = jz4740_pcm_preallocate_dma_buffer(pcm,
322                         SNDRV_PCM_STREAM_CAPTURE);
323                 if (ret)
324                         goto err;
325         }
326
327 err:
328         return ret;
329 }
330
331 static struct snd_soc_platform_driver jz4740_soc_platform = {
332                 .ops            = &jz4740_pcm_ops,
333                 .pcm_new        = jz4740_pcm_new,
334                 .pcm_free       = jz4740_pcm_free,
335 };
336
337 static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
338 {
339         return snd_soc_register_platform(&pdev->dev, &jz4740_soc_platform);
340 }
341
342 static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
343 {
344         snd_soc_unregister_platform(&pdev->dev);
345         return 0;
346 }
347
348 static struct platform_driver jz4740_pcm_driver = {
349         .probe = jz4740_pcm_probe,
350         .remove = __devexit_p(jz4740_pcm_remove),
351         .driver = {
352                 .name = "jz4740-pcm-audio",
353                 .owner = THIS_MODULE,
354         },
355 };
356
357 static int __init jz4740_soc_platform_init(void)
358 {
359         return platform_driver_register(&jz4740_pcm_driver);
360 }
361 module_init(jz4740_soc_platform_init);
362
363 static void __exit jz4740_soc_platform_exit(void)
364 {
365         return platform_driver_unregister(&jz4740_pcm_driver);
366 }
367 module_exit(jz4740_soc_platform_exit);
368
369 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
370 MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
371 MODULE_LICENSE("GPL");