diff options
author | Stephen Warren <swarren@nvidia.com> | 2011-01-08 00:36:13 -0500 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2011-01-10 17:20:29 -0500 |
commit | 7605eb5bc327497aed1816d6238d3b64e032b491 (patch) | |
tree | 0cb6357c059724d3eec03c42823ee333c3e6741d /sound | |
parent | f0d8af4f528ee6dd63670521d429edac67934c06 (diff) |
ASoC: tegra: Add tegra-pcm driver
This provides an ASoC platform driver that manages Tegra's APB DMA
controller.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/tegra/tegra_pcm.c | 401 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_pcm.h | 55 |
2 files changed, 456 insertions, 0 deletions
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000000..663ea9fa0ca3 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c | |||
@@ -0,0 +1,401 @@ | |||
1 | /* | ||
2 | * tegra_pcm.c - Tegra PCM driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * Vijay Mali <vmali@nvidia.com> | ||
12 | * | ||
13 | * Copyright (C) 2010 Google, Inc. | ||
14 | * Iliyan Malchev <malchev@google.com> | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or | ||
17 | * modify it under the terms of the GNU General Public License | ||
18 | * version 2 as published by the Free Software Foundation. | ||
19 | * | ||
20 | * This program is distributed in the hope that it will be useful, but | ||
21 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
23 | * General Public License for more details. | ||
24 | * | ||
25 | * You should have received a copy of the GNU General Public License | ||
26 | * along with this program; if not, write to the Free Software | ||
27 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
28 | * 02110-1301 USA | ||
29 | * | ||
30 | */ | ||
31 | |||
32 | #include <linux/module.h> | ||
33 | #include <linux/dma-mapping.h> | ||
34 | #include <linux/slab.h> | ||
35 | #include <sound/core.h> | ||
36 | #include <sound/pcm.h> | ||
37 | #include <sound/pcm_params.h> | ||
38 | #include <sound/soc.h> | ||
39 | |||
40 | #include "tegra_pcm.h" | ||
41 | |||
42 | static const struct snd_pcm_hardware tegra_pcm_hardware = { | ||
43 | .info = SNDRV_PCM_INFO_MMAP | | ||
44 | SNDRV_PCM_INFO_MMAP_VALID | | ||
45 | SNDRV_PCM_INFO_PAUSE | | ||
46 | SNDRV_PCM_INFO_RESUME | | ||
47 | SNDRV_PCM_INFO_INTERLEAVED, | ||
48 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
49 | .channels_min = 2, | ||
50 | .channels_max = 2, | ||
51 | .period_bytes_min = 1024, | ||
52 | .period_bytes_max = PAGE_SIZE, | ||
53 | .periods_min = 2, | ||
54 | .periods_max = 8, | ||
55 | .buffer_bytes_max = PAGE_SIZE * 8, | ||
56 | .fifo_size = 4, | ||
57 | }; | ||
58 | |||
59 | static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd) | ||
60 | { | ||
61 | struct snd_pcm_substream *substream = prtd->substream; | ||
62 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
63 | struct tegra_dma_req *dma_req; | ||
64 | unsigned long addr; | ||
65 | |||
66 | dma_req = &prtd->dma_req[prtd->dma_req_idx]; | ||
67 | prtd->dma_req_idx = 1 - prtd->dma_req_idx; | ||
68 | |||
69 | addr = buf->addr + prtd->dma_pos; | ||
70 | prtd->dma_pos += dma_req->size; | ||
71 | if (prtd->dma_pos >= prtd->dma_pos_end) | ||
72 | prtd->dma_pos = 0; | ||
73 | |||
74 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
75 | dma_req->source_addr = addr; | ||
76 | else | ||
77 | dma_req->dest_addr = addr; | ||
78 | |||
79 | tegra_dma_enqueue_req(prtd->dma_chan, dma_req); | ||
80 | } | ||
81 | |||
82 | static void dma_complete_callback(struct tegra_dma_req *req) | ||
83 | { | ||
84 | struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; | ||
85 | struct snd_pcm_substream *substream = prtd->substream; | ||
86 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
87 | |||
88 | spin_lock(&prtd->lock); | ||
89 | |||
90 | if (!prtd->running) { | ||
91 | spin_unlock(&prtd->lock); | ||
92 | return; | ||
93 | } | ||
94 | |||
95 | if (++prtd->period_index >= runtime->periods) | ||
96 | prtd->period_index = 0; | ||
97 | |||
98 | tegra_pcm_queue_dma(prtd); | ||
99 | |||
100 | spin_unlock(&prtd->lock); | ||
101 | |||
102 | snd_pcm_period_elapsed(substream); | ||
103 | } | ||
104 | |||
105 | static void setup_dma_tx_request(struct tegra_dma_req *req, | ||
106 | struct tegra_pcm_dma_params * dmap) | ||
107 | { | ||
108 | req->complete = dma_complete_callback; | ||
109 | req->to_memory = false; | ||
110 | req->dest_addr = dmap->addr; | ||
111 | req->dest_wrap = dmap->wrap; | ||
112 | req->source_bus_width = 32; | ||
113 | req->source_wrap = 0; | ||
114 | req->dest_bus_width = dmap->width; | ||
115 | req->req_sel = dmap->req_sel; | ||
116 | } | ||
117 | |||
118 | static void setup_dma_rx_request(struct tegra_dma_req *req, | ||
119 | struct tegra_pcm_dma_params * dmap) | ||
120 | { | ||
121 | req->complete = dma_complete_callback; | ||
122 | req->to_memory = true; | ||
123 | req->source_addr = dmap->addr; | ||
124 | req->dest_wrap = 0; | ||
125 | req->source_bus_width = dmap->width; | ||
126 | req->source_wrap = dmap->wrap; | ||
127 | req->dest_bus_width = 32; | ||
128 | req->req_sel = dmap->req_sel; | ||
129 | } | ||
130 | |||
131 | static int tegra_pcm_open(struct snd_pcm_substream *substream) | ||
132 | { | ||
133 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
134 | struct tegra_runtime_data *prtd; | ||
135 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
136 | struct tegra_pcm_dma_params * dmap; | ||
137 | int ret = 0; | ||
138 | |||
139 | prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL); | ||
140 | if (prtd == NULL) | ||
141 | return -ENOMEM; | ||
142 | |||
143 | runtime->private_data = prtd; | ||
144 | prtd->substream = substream; | ||
145 | |||
146 | spin_lock_init(&prtd->lock); | ||
147 | |||
148 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
149 | dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
150 | setup_dma_tx_request(&prtd->dma_req[0], dmap); | ||
151 | setup_dma_tx_request(&prtd->dma_req[1], dmap); | ||
152 | } else { | ||
153 | dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
154 | setup_dma_rx_request(&prtd->dma_req[0], dmap); | ||
155 | setup_dma_rx_request(&prtd->dma_req[1], dmap); | ||
156 | } | ||
157 | |||
158 | prtd->dma_req[0].dev = prtd; | ||
159 | prtd->dma_req[1].dev = prtd; | ||
160 | |||
161 | prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); | ||
162 | if (IS_ERR(prtd->dma_chan)) { | ||
163 | ret = PTR_ERR(prtd->dma_chan); | ||
164 | goto err; | ||
165 | } | ||
166 | |||
167 | /* Set HW params now that initialization is complete */ | ||
168 | snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); | ||
169 | |||
170 | /* Ensure that buffer size is a multiple of period size */ | ||
171 | ret = snd_pcm_hw_constraint_integer(runtime, | ||
172 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
173 | if (ret < 0) | ||
174 | goto err; | ||
175 | |||
176 | return 0; | ||
177 | |||
178 | err: | ||
179 | if (prtd->dma_chan) { | ||
180 | tegra_dma_free_channel(prtd->dma_chan); | ||
181 | } | ||
182 | |||
183 | kfree(prtd); | ||
184 | |||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | static int tegra_pcm_close(struct snd_pcm_substream *substream) | ||
189 | { | ||
190 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
191 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
192 | |||
193 | tegra_dma_free_channel(prtd->dma_chan); | ||
194 | |||
195 | kfree(prtd); | ||
196 | |||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | static int tegra_pcm_hw_params(struct snd_pcm_substream *substream, | ||
201 | struct snd_pcm_hw_params *params) | ||
202 | { | ||
203 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
204 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
205 | |||
206 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
207 | |||
208 | prtd->dma_req[0].size = params_period_bytes(params); | ||
209 | prtd->dma_req[1].size = prtd->dma_req[0].size; | ||
210 | |||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static int tegra_pcm_hw_free(struct snd_pcm_substream *substream) | ||
215 | { | ||
216 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
217 | |||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
222 | { | ||
223 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
224 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
225 | unsigned long flags; | ||
226 | |||
227 | switch (cmd) { | ||
228 | case SNDRV_PCM_TRIGGER_START: | ||
229 | prtd->dma_pos = 0; | ||
230 | prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size); | ||
231 | prtd->period_index = 0; | ||
232 | prtd->dma_req_idx = 0; | ||
233 | /* Fall-through */ | ||
234 | case SNDRV_PCM_TRIGGER_RESUME: | ||
235 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
236 | spin_lock_irqsave(&prtd->lock, flags); | ||
237 | prtd->running = 1; | ||
238 | spin_unlock_irqrestore(&prtd->lock, flags); | ||
239 | tegra_pcm_queue_dma(prtd); | ||
240 | tegra_pcm_queue_dma(prtd); | ||
241 | break; | ||
242 | case SNDRV_PCM_TRIGGER_STOP: | ||
243 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
244 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
245 | spin_lock_irqsave(&prtd->lock, flags); | ||
246 | prtd->running = 0; | ||
247 | spin_unlock_irqrestore(&prtd->lock, flags); | ||
248 | tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]); | ||
249 | tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]); | ||
250 | break; | ||
251 | default: | ||
252 | return -EINVAL; | ||
253 | } | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) | ||
259 | { | ||
260 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
261 | struct tegra_runtime_data *prtd = runtime->private_data; | ||
262 | |||
263 | return prtd->period_index * runtime->period_size; | ||
264 | } | ||
265 | |||
266 | |||
267 | static int tegra_pcm_mmap(struct snd_pcm_substream *substream, | ||
268 | struct vm_area_struct *vma) | ||
269 | { | ||
270 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
271 | |||
272 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | ||
273 | runtime->dma_area, | ||
274 | runtime->dma_addr, | ||
275 | runtime->dma_bytes); | ||
276 | } | ||
277 | |||
278 | static struct snd_pcm_ops tegra_pcm_ops = { | ||
279 | .open = tegra_pcm_open, | ||
280 | .close = tegra_pcm_close, | ||
281 | .ioctl = snd_pcm_lib_ioctl, | ||
282 | .hw_params = tegra_pcm_hw_params, | ||
283 | .hw_free = tegra_pcm_hw_free, | ||
284 | .trigger = tegra_pcm_trigger, | ||
285 | .pointer = tegra_pcm_pointer, | ||
286 | .mmap = tegra_pcm_mmap, | ||
287 | }; | ||
288 | |||
289 | static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
290 | { | ||
291 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
292 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
293 | size_t size = tegra_pcm_hardware.buffer_bytes_max; | ||
294 | |||
295 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | ||
296 | &buf->addr, GFP_KERNEL); | ||
297 | if (!buf->area) | ||
298 | return -ENOMEM; | ||
299 | |||
300 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
301 | buf->dev.dev = pcm->card->dev; | ||
302 | buf->private_data = NULL; | ||
303 | buf->bytes = size; | ||
304 | |||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
309 | { | ||
310 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
311 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
312 | |||
313 | if (!buf->area) | ||
314 | return; | ||
315 | |||
316 | dma_free_writecombine(pcm->card->dev, buf->bytes, | ||
317 | buf->area, buf->addr); | ||
318 | buf->area = NULL; | ||
319 | } | ||
320 | |||
321 | static u64 tegra_dma_mask = DMA_BIT_MASK(32); | ||
322 | |||
323 | static int tegra_pcm_new(struct snd_card *card, | ||
324 | struct snd_soc_dai *dai, struct snd_pcm *pcm) | ||
325 | { | ||
326 | int ret = 0; | ||
327 | |||
328 | if (!card->dev->dma_mask) | ||
329 | card->dev->dma_mask = &tegra_dma_mask; | ||
330 | if (!card->dev->coherent_dma_mask) | ||
331 | card->dev->coherent_dma_mask = 0xffffffff; | ||
332 | |||
333 | if (dai->driver->playback.channels_min) { | ||
334 | ret = tegra_pcm_preallocate_dma_buffer(pcm, | ||
335 | SNDRV_PCM_STREAM_PLAYBACK); | ||
336 | if (ret) | ||
337 | goto err; | ||
338 | } | ||
339 | |||
340 | if (dai->driver->capture.channels_min) { | ||
341 | ret = tegra_pcm_preallocate_dma_buffer(pcm, | ||
342 | SNDRV_PCM_STREAM_CAPTURE); | ||
343 | if (ret) | ||
344 | goto err_free_play; | ||
345 | } | ||
346 | |||
347 | return 0; | ||
348 | |||
349 | err_free_play: | ||
350 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); | ||
351 | err: | ||
352 | return ret; | ||
353 | } | ||
354 | |||
355 | static void tegra_pcm_free(struct snd_pcm *pcm) | ||
356 | { | ||
357 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); | ||
358 | tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); | ||
359 | } | ||
360 | |||
361 | struct snd_soc_platform_driver tegra_pcm_platform = { | ||
362 | .ops = &tegra_pcm_ops, | ||
363 | .pcm_new = tegra_pcm_new, | ||
364 | .pcm_free = tegra_pcm_free, | ||
365 | }; | ||
366 | |||
367 | static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev) | ||
368 | { | ||
369 | return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform); | ||
370 | } | ||
371 | |||
372 | static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev) | ||
373 | { | ||
374 | snd_soc_unregister_platform(&pdev->dev); | ||
375 | return 0; | ||
376 | } | ||
377 | |||
378 | static struct platform_driver tegra_pcm_driver = { | ||
379 | .driver = { | ||
380 | .name = "tegra-pcm-audio", | ||
381 | .owner = THIS_MODULE, | ||
382 | }, | ||
383 | .probe = tegra_pcm_platform_probe, | ||
384 | .remove = __devexit_p(tegra_pcm_platform_remove), | ||
385 | }; | ||
386 | |||
387 | static int __init snd_tegra_pcm_init(void) | ||
388 | { | ||
389 | return platform_driver_register(&tegra_pcm_driver); | ||
390 | } | ||
391 | module_init(snd_tegra_pcm_init); | ||
392 | |||
393 | static void __exit snd_tegra_pcm_exit(void) | ||
394 | { | ||
395 | platform_driver_unregister(&tegra_pcm_driver); | ||
396 | } | ||
397 | module_exit(snd_tegra_pcm_exit); | ||
398 | |||
399 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | ||
400 | MODULE_DESCRIPTION("Tegra PCM ASoC driver"); | ||
401 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 000000000000..dbb90339fe0d --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * tegra_pcm.h - Definitions for Tegra PCM driver | ||
3 | * | ||
4 | * Author: Stephen Warren <swarren@nvidia.com> | ||
5 | * Copyright (C) 2010 - NVIDIA, Inc. | ||
6 | * | ||
7 | * Based on code copyright/by: | ||
8 | * | ||
9 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
10 | * Scott Peterson <speterson@nvidia.com> | ||
11 | * | ||
12 | * Copyright (C) 2010 Google, Inc. | ||
13 | * Iliyan Malchev <malchev@google.com> | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License | ||
17 | * version 2 as published by the Free Software Foundation. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, but | ||
20 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
22 | * General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
27 | * 02110-1301 USA | ||
28 | * | ||
29 | */ | ||
30 | |||
31 | #ifndef __TEGRA_PCM_H__ | ||
32 | #define __TEGRA_PCM_H__ | ||
33 | |||
34 | #include <mach/dma.h> | ||
35 | |||
36 | struct tegra_pcm_dma_params { | ||
37 | unsigned long addr; | ||
38 | unsigned long wrap; | ||
39 | unsigned long width; | ||
40 | unsigned long req_sel; | ||
41 | }; | ||
42 | |||
43 | struct tegra_runtime_data { | ||
44 | struct snd_pcm_substream *substream; | ||
45 | spinlock_t lock; | ||
46 | int running; | ||
47 | int dma_pos; | ||
48 | int dma_pos_end; | ||
49 | int period_index; | ||
50 | int dma_req_idx; | ||
51 | struct tegra_dma_req dma_req[2]; | ||
52 | struct tegra_dma_channel *dma_chan; | ||
53 | }; | ||
54 | |||
55 | #endif | ||