]> git.karo-electronics.de Git - mv-sheeva.git/blob - sound/soc/ep93xx/ep93xx-pcm.c
ASoC: multi-component - ASoC Multi-Component Support
[mv-sheeva.git] / sound / soc / ep93xx / ep93xx-pcm.c
1 /*
2  * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
3  *
4  * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
5  * Copyright (C) 2006 Applied Data Systems
6  *
7  * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
8  *   Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 2 as
12  * published by the Free Software Foundation.
13  */
14
15 #include <linux/module.h>
16 #include <linux/init.h>
17 #include <linux/device.h>
18 #include <linux/slab.h>
19 #include <linux/dma-mapping.h>
20
21 #include <sound/core.h>
22 #include <sound/pcm.h>
23 #include <sound/pcm_params.h>
24 #include <sound/soc.h>
25
26 #include <mach/dma.h>
27 #include <mach/hardware.h>
28 #include <mach/ep93xx-regs.h>
29
30 #include "ep93xx-pcm.h"
31
32 static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
33         .info                   = (SNDRV_PCM_INFO_MMAP          |
34                                    SNDRV_PCM_INFO_MMAP_VALID    |
35                                    SNDRV_PCM_INFO_INTERLEAVED   |
36                                    SNDRV_PCM_INFO_BLOCK_TRANSFER),
37                                    
38         .rates                  = SNDRV_PCM_RATE_8000_48000,
39         .rate_min               = SNDRV_PCM_RATE_8000,
40         .rate_max               = SNDRV_PCM_RATE_48000,
41         
42         .formats                = (SNDRV_PCM_FMTBIT_S16_LE |
43                                    SNDRV_PCM_FMTBIT_S24_LE |
44                                    SNDRV_PCM_FMTBIT_S32_LE),
45         
46         .buffer_bytes_max       = 131072,
47         .period_bytes_min       = 32,
48         .period_bytes_max       = 32768,
49         .periods_min            = 1,
50         .periods_max            = 32,
51         .fifo_size              = 32,
52 };
53
54 struct ep93xx_runtime_data
55 {
56         struct ep93xx_dma_m2p_client    cl;
57         struct ep93xx_pcm_dma_params    *params;
58         int                             pointer_bytes;
59         struct tasklet_struct           period_tasklet;
60         int                             periods;
61         struct ep93xx_dma_buffer        buf[32];
62 };
63
64 static void ep93xx_pcm_period_elapsed(unsigned long data)
65 {
66         struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
67         snd_pcm_period_elapsed(substream);
68 }
69
70 static void ep93xx_pcm_buffer_started(void *cookie,
71                                       struct ep93xx_dma_buffer *buf)
72 {
73 }
74
75 static void ep93xx_pcm_buffer_finished(void *cookie, 
76                                        struct ep93xx_dma_buffer *buf, 
77                                        int bytes, int error)
78 {
79         struct snd_pcm_substream *substream = cookie;
80         struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
81
82         if (buf == rtd->buf + rtd->periods - 1)
83                 rtd->pointer_bytes = 0;
84         else
85                 rtd->pointer_bytes += buf->size;
86
87         if (!error) {
88                 ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf);
89                 tasklet_schedule(&rtd->period_tasklet);
90         } else {
91                 snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
92         }
93 }
94
95 static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
96 {
97         struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
98         struct snd_soc_dai *cpu_dai = soc_rtd->cpu_dai;
99         struct ep93xx_pcm_dma_params *dma_params;
100         struct ep93xx_runtime_data *rtd;    
101         int ret;
102
103         dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
104         snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
105
106         rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
107         if (!rtd) 
108                 return -ENOMEM;
109
110         memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet));
111         rtd->period_tasklet.func = ep93xx_pcm_period_elapsed;
112         rtd->period_tasklet.data = (unsigned long)substream;
113
114         rtd->cl.name = dma_params->name;
115         rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR |
116                 ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
117                  EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX);
118         rtd->cl.cookie = substream;
119         rtd->cl.buffer_started = ep93xx_pcm_buffer_started;
120         rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished;
121         ret = ep93xx_dma_m2p_client_register(&rtd->cl);
122         if (ret < 0) {
123                 kfree(rtd);
124                 return ret;
125         }
126         
127         substream->runtime->private_data = rtd;
128         return 0;
129 }
130
131 static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
132 {
133         struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
134
135         ep93xx_dma_m2p_client_unregister(&rtd->cl);
136         kfree(rtd);
137         return 0;
138 }
139
140 static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
141                                 struct snd_pcm_hw_params *params)
142 {
143         struct snd_pcm_runtime *runtime = substream->runtime;
144         struct ep93xx_runtime_data *rtd = runtime->private_data;
145         size_t totsize = params_buffer_bytes(params);
146         size_t period = params_period_bytes(params);
147         int i;
148
149         snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
150         runtime->dma_bytes = totsize;
151
152         rtd->periods = (totsize + period - 1) / period;
153         for (i = 0; i < rtd->periods; i++) {
154                 rtd->buf[i].bus_addr = runtime->dma_addr + (i * period);
155                 rtd->buf[i].size = period;
156                 if ((i + 1) * period > totsize)
157                         rtd->buf[i].size = totsize - (i * period);
158         }
159
160         return 0;
161 }
162
163 static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
164 {
165         snd_pcm_set_runtime_buffer(substream, NULL);
166         return 0;
167 }
168
169 static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
170 {
171         struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
172         int ret;
173         int i;
174
175         ret = 0;
176         switch (cmd) {
177         case SNDRV_PCM_TRIGGER_START:
178         case SNDRV_PCM_TRIGGER_RESUME:
179         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
180                 rtd->pointer_bytes = 0;
181                 for (i = 0; i < rtd->periods; i++)
182                         ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i);
183                 break;
184
185         case SNDRV_PCM_TRIGGER_STOP:
186         case SNDRV_PCM_TRIGGER_SUSPEND:
187         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
188                 ep93xx_dma_m2p_flush(&rtd->cl);
189                 break;
190
191         default:
192                 ret = -EINVAL;
193                 break;
194         }
195
196         return ret;
197 }
198
199 static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
200 {
201         struct snd_pcm_runtime *runtime = substream->runtime;
202         struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
203
204         /* FIXME: implement this with sub-period granularity */
205         return bytes_to_frames(runtime, rtd->pointer_bytes);
206 }
207
208 static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream,
209                            struct vm_area_struct *vma)
210 {
211         struct snd_pcm_runtime *runtime = substream->runtime;
212
213         return dma_mmap_writecombine(substream->pcm->card->dev, vma,
214                                      runtime->dma_area,
215                                      runtime->dma_addr,
216                                      runtime->dma_bytes);
217 }
218
219 static struct snd_pcm_ops ep93xx_pcm_ops = {
220         .open           = ep93xx_pcm_open,
221         .close          = ep93xx_pcm_close,
222         .ioctl          = snd_pcm_lib_ioctl,
223         .hw_params      = ep93xx_pcm_hw_params,
224         .hw_free        = ep93xx_pcm_hw_free,
225         .trigger        = ep93xx_pcm_trigger,
226         .pointer        = ep93xx_pcm_pointer,
227         .mmap           = ep93xx_pcm_mmap,
228 };
229
230 static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
231 {
232         struct snd_pcm_substream *substream = pcm->streams[stream].substream;
233         struct snd_dma_buffer *buf = &substream->dma_buffer;
234         size_t size = ep93xx_pcm_hardware.buffer_bytes_max;
235
236         buf->dev.type = SNDRV_DMA_TYPE_DEV;
237         buf->dev.dev = pcm->card->dev;
238         buf->private_data = NULL;
239         buf->area = dma_alloc_writecombine(pcm->card->dev, size,
240                                            &buf->addr, GFP_KERNEL);
241         buf->bytes = size;
242
243         return (buf->area == NULL) ? -ENOMEM : 0;
244 }
245
246 static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
247 {
248         struct snd_pcm_substream *substream;
249         struct snd_dma_buffer *buf;
250         int stream;
251
252         for (stream = 0; stream < 2; stream++) {                
253                 substream = pcm->streams[stream].substream;
254                 if (!substream)
255                         continue;
256                 
257                 buf = &substream->dma_buffer;
258                 if (!buf->area)
259                         continue;
260
261                 dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area,
262                                       buf->addr);
263                 buf->area = NULL;
264         }
265 }
266
267 static u64 ep93xx_pcm_dmamask = 0xffffffff;
268
269 static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
270                           struct snd_pcm *pcm)
271 {
272         int ret = 0;
273
274         if (!card->dev->dma_mask)
275                 card->dev->dma_mask = &ep93xx_pcm_dmamask;
276         if (!card->dev->coherent_dma_mask)
277                 card->dev->coherent_dma_mask = 0xffffffff;
278
279         if (dai->driver->playback.channels_min) {
280                 ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
281                                         SNDRV_PCM_STREAM_PLAYBACK);
282                 if (ret)
283                         return ret;
284         }
285
286         if (dai->driver->capture.channels_min) {
287                 ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
288                                         SNDRV_PCM_STREAM_CAPTURE);
289                 if (ret)
290                         return ret;
291         }
292
293         return 0;
294 }
295
296 static struct snd_soc_platform_driver ep93xx_soc_platform = {
297         .ops            = &ep93xx_pcm_ops,
298         .pcm_new        = &ep93xx_pcm_new,
299         .pcm_free       = &ep93xx_pcm_free_dma_buffers,
300 };
301
302 static int __devinit ep93xx_soc_platform_probe(struct platform_device *pdev)
303 {
304         return snd_soc_register_platform(&pdev->dev, &ep93xx_soc_platform);
305 }
306
307 static int __devexit ep93xx_soc_platform_remove(struct platform_device *pdev)
308 {
309         snd_soc_unregister_platform(&pdev->dev);
310         return 0;
311 }
312
313 static struct platform_driver ep93xx_pcm_driver = {
314         .driver = {
315                         .name = "ep93xx-pcm-audio",
316                         .owner = THIS_MODULE,
317         },
318
319         .probe = ep93xx_soc_platform_probe,
320         .remove = __devexit_p(ep93xx_soc_platform_remove),
321 };
322
323 static int __init ep93xx_soc_platform_init(void)
324 {
325         return platform_driver_register(&ep93xx_pcm_driver);
326 }
327
328 static void __exit ep93xx_soc_platform_exit(void)
329 {
330         platform_driver_unregister(&ep93xx_pcm_driver);
331 }
332
333 module_init(ep93xx_soc_platform_init);
334 module_exit(ep93xx_soc_platform_exit);
335
336 MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
337 MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
338 MODULE_LICENSE("GPL");