diff options
Diffstat (limited to 'sound/soc/ep93xx/ep93xx-pcm.c')
-rw-r--r-- | sound/soc/ep93xx/ep93xx-pcm.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c new file mode 100644 index 000000000000..4ba938400791 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-pcm.c | |||
@@ -0,0 +1,319 @@ | |||
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->dai->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->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->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 | struct snd_soc_platform ep93xx_soc_platform = { | ||
297 | .name = "ep93xx-audio", | ||
298 | .pcm_ops = &ep93xx_pcm_ops, | ||
299 | .pcm_new = &ep93xx_pcm_new, | ||
300 | .pcm_free = &ep93xx_pcm_free_dma_buffers, | ||
301 | }; | ||
302 | EXPORT_SYMBOL_GPL(ep93xx_soc_platform); | ||
303 | |||
304 | static int __init ep93xx_soc_platform_init(void) | ||
305 | { | ||
306 | return snd_soc_register_platform(&ep93xx_soc_platform); | ||
307 | } | ||
308 | |||
309 | static void __exit ep93xx_soc_platform_exit(void) | ||
310 | { | ||
311 | snd_soc_unregister_platform(&ep93xx_soc_platform); | ||
312 | } | ||
313 | |||
314 | module_init(ep93xx_soc_platform_init); | ||
315 | module_exit(ep93xx_soc_platform_exit); | ||
316 | |||
317 | MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>"); | ||
318 | MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); | ||
319 | MODULE_LICENSE("GPL"); | ||