Root/
Source at commit 2990a784724c353aca3500eacf2f0ef2d5881e7f created 12 years 7 months ago. By Maarten ter Huurne, MIPS: A320: Add SoC sound support for Dingoo A320. | |
---|---|
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 | if (prtd->dma_pos == prtd->dma_end) |
66 | prtd->dma_pos = prtd->dma_start; |
67 | |
68 | jz4740_dma_disable(prtd->dma); |
69 | |
70 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
71 | jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos); |
72 | jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr); |
73 | } else { |
74 | jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr); |
75 | jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos); |
76 | } |
77 | |
78 | jz4740_dma_set_transfer_count(prtd->dma, prtd->dma_period); |
79 | |
80 | prtd->dma_pos += prtd->dma_period; |
81 | |
82 | jz4740_dma_enable(prtd->dma); |
83 | } |
84 | |
85 | static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err, |
86 | void *dev_id) |
87 | { |
88 | struct snd_pcm_substream *substream = dev_id; |
89 | struct snd_pcm_runtime *runtime = substream->runtime; |
90 | struct jz4740_runtime_data *prtd = runtime->private_data; |
91 | |
92 | snd_pcm_period_elapsed(substream); |
93 | |
94 | jz4740_pcm_start_transfer(prtd, substream); |
95 | } |
96 | |
97 | static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream, |
98 | struct snd_pcm_hw_params *params) |
99 | { |
100 | struct snd_pcm_runtime *runtime = substream->runtime; |
101 | struct jz4740_runtime_data *prtd = runtime->private_data; |
102 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
103 | struct jz4740_pcm_config *config; |
104 | |
105 | config = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); |
106 | |
107 | if (!config) |
108 | return 0; |
109 | |
110 | if (!prtd->dma) { |
111 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
112 | prtd->dma = jz4740_dma_request(substream, |
113 | "PCM Capture", 0); |
114 | else |
115 | prtd->dma = jz4740_dma_request(substream, |
116 | "PCM Playback", 0); |
117 | } |
118 | |
119 | if (!prtd->dma) |
120 | return -EBUSY; |
121 | |
122 | jz4740_dma_configure(prtd->dma, &config->dma_config); |
123 | prtd->fifo_addr = config->fifo_addr; |
124 | |
125 | jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done); |
126 | |
127 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); |
128 | runtime->dma_bytes = params_buffer_bytes(params); |
129 | |
130 | prtd->dma_period = params_period_bytes(params); |
131 | prtd->dma_start = runtime->dma_addr; |
132 | prtd->dma_pos = prtd->dma_start; |
133 | prtd->dma_end = prtd->dma_start + runtime->dma_bytes; |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream) |
139 | { |
140 | struct jz4740_runtime_data *prtd = substream->runtime->private_data; |
141 | |
142 | snd_pcm_set_runtime_buffer(substream, NULL); |
143 | if (prtd->dma) { |
144 | jz4740_dma_free(prtd->dma); |
145 | prtd->dma = NULL; |
146 | } |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static int jz4740_pcm_prepare(struct snd_pcm_substream *substream) |
152 | { |
153 | struct jz4740_runtime_data *prtd = substream->runtime->private_data; |
154 | |
155 | if (!prtd->dma) |
156 | return -EBUSY; |
157 | |
158 | prtd->dma_pos = prtd->dma_start; |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
164 | { |
165 | struct snd_pcm_runtime *runtime = substream->runtime; |
166 | struct jz4740_runtime_data *prtd = runtime->private_data; |
167 | |
168 | switch (cmd) { |
169 | case SNDRV_PCM_TRIGGER_START: |
170 | case SNDRV_PCM_TRIGGER_RESUME: |
171 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
172 | jz4740_pcm_start_transfer(prtd, substream); |
173 | break; |
174 | case SNDRV_PCM_TRIGGER_STOP: |
175 | case SNDRV_PCM_TRIGGER_SUSPEND: |
176 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
177 | jz4740_dma_disable(prtd->dma); |
178 | break; |
179 | default: |
180 | break; |
181 | } |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream) |
187 | { |
188 | struct snd_pcm_runtime *runtime = substream->runtime; |
189 | struct jz4740_runtime_data *prtd = runtime->private_data; |
190 | unsigned long byte_offset; |
191 | snd_pcm_uframes_t offset; |
192 | struct jz4740_dma_chan *dma = prtd->dma; |
193 | |
194 | /* prtd->dma_pos points to the end of the current transfer. So by |
195 | * subtracting prdt->dma_start we get the offset to the end of the |
196 | * current period in bytes. By subtracting the residue of the transfer |
197 | * we get the current offset in bytes. */ |
198 | byte_offset = prtd->dma_pos - prtd->dma_start; |
199 | byte_offset -= jz4740_dma_get_residue(dma); |
200 | |
201 | offset = bytes_to_frames(runtime, byte_offset); |
202 | if (offset >= runtime->buffer_size) |
203 | offset = 0; |
204 | |
205 | return offset; |
206 | } |
207 | |
208 | static int jz4740_pcm_open(struct snd_pcm_substream *substream) |
209 | { |
210 | struct snd_pcm_runtime *runtime = substream->runtime; |
211 | struct jz4740_runtime_data *prtd; |
212 | int ret; |
213 | |
214 | prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); |
215 | if (prtd == NULL) |
216 | return -ENOMEM; |
217 | |
218 | snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware); |
219 | |
220 | ret = snd_pcm_hw_constraint_integer(runtime, |
221 | SNDRV_PCM_HW_PARAM_PERIODS); |
222 | if (ret < 0) { |
223 | kfree(prtd); |
224 | return ret; |
225 | } |
226 | |
227 | runtime->private_data = prtd; |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static int jz4740_pcm_close(struct snd_pcm_substream *substream) |
233 | { |
234 | struct snd_pcm_runtime *runtime = substream->runtime; |
235 | struct jz4740_runtime_data *prtd = runtime->private_data; |
236 | |
237 | kfree(prtd); |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int jz4740_pcm_mmap(struct snd_pcm_substream *substream, |
243 | struct vm_area_struct *vma) |
244 | { |
245 | return remap_pfn_range(vma, vma->vm_start, |
246 | substream->dma_buffer.addr >> PAGE_SHIFT, |
247 | vma->vm_end - vma->vm_start, vma->vm_page_prot); |
248 | } |
249 | |
250 | static struct snd_pcm_ops jz4740_pcm_ops = { |
251 | .open = jz4740_pcm_open, |
252 | .close = jz4740_pcm_close, |
253 | .ioctl = snd_pcm_lib_ioctl, |
254 | .hw_params = jz4740_pcm_hw_params, |
255 | .hw_free = jz4740_pcm_hw_free, |
256 | .prepare = jz4740_pcm_prepare, |
257 | .trigger = jz4740_pcm_trigger, |
258 | .pointer = jz4740_pcm_pointer, |
259 | .mmap = jz4740_pcm_mmap, |
260 | }; |
261 | |
262 | static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) |
263 | { |
264 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; |
265 | struct snd_dma_buffer *buf = &substream->dma_buffer; |
266 | size_t size = jz4740_pcm_hardware.buffer_bytes_max; |
267 | |
268 | buf->dev.type = SNDRV_DMA_TYPE_DEV; |
269 | buf->dev.dev = pcm->card->dev; |
270 | buf->private_data = NULL; |
271 | |
272 | buf->area = dma_alloc_noncoherent(pcm->card->dev, size, |
273 | &buf->addr, GFP_KERNEL); |
274 | if (!buf->area) |
275 | return -ENOMEM; |
276 | |
277 | buf->bytes = size; |
278 | |
279 | return 0; |
280 | } |
281 | |
282 | static void jz4740_pcm_free(struct snd_pcm *pcm) |
283 | { |
284 | struct snd_pcm_substream *substream; |
285 | struct snd_dma_buffer *buf; |
286 | int stream; |
287 | |
288 | for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) { |
289 | substream = pcm->streams[stream].substream; |
290 | if (!substream) |
291 | continue; |
292 | |
293 | buf = &substream->dma_buffer; |
294 | if (!buf->area) |
295 | continue; |
296 | |
297 | dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area, |
298 | buf->addr); |
299 | buf->area = NULL; |
300 | } |
301 | } |
302 | |
303 | static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32); |
304 | |
305 | static int jz4740_pcm_new(struct snd_soc_pcm_runtime *rtd) |
306 | { |
307 | struct snd_card *card = rtd->card->snd_card; |
308 | struct snd_pcm *pcm = rtd->pcm; |
309 | int ret = 0; |
310 | |
311 | if (!card->dev->dma_mask) |
312 | card->dev->dma_mask = &jz4740_pcm_dmamask; |
313 | |
314 | if (!card->dev->coherent_dma_mask) |
315 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); |
316 | |
317 | if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { |
318 | ret = jz4740_pcm_preallocate_dma_buffer(pcm, |
319 | SNDRV_PCM_STREAM_PLAYBACK); |
320 | if (ret) |
321 | goto err; |
322 | } |
323 | |
324 | if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { |
325 | ret = jz4740_pcm_preallocate_dma_buffer(pcm, |
326 | SNDRV_PCM_STREAM_CAPTURE); |
327 | if (ret) |
328 | goto err; |
329 | } |
330 | |
331 | err: |
332 | return ret; |
333 | } |
334 | |
335 | static struct snd_soc_platform_driver jz4740_soc_platform = { |
336 | .ops = &jz4740_pcm_ops, |
337 | .pcm_new = jz4740_pcm_new, |
338 | .pcm_free = jz4740_pcm_free, |
339 | }; |
340 | |
341 | static int __devinit jz4740_pcm_probe(struct platform_device *pdev) |
342 | { |
343 | return snd_soc_register_platform(&pdev->dev, &jz4740_soc_platform); |
344 | } |
345 | |
346 | static int __devexit jz4740_pcm_remove(struct platform_device *pdev) |
347 | { |
348 | snd_soc_unregister_platform(&pdev->dev); |
349 | return 0; |
350 | } |
351 | |
352 | static struct platform_driver jz4740_pcm_driver = { |
353 | .probe = jz4740_pcm_probe, |
354 | .remove = __devexit_p(jz4740_pcm_remove), |
355 | .driver = { |
356 | .name = "jz4740-pcm-audio", |
357 | .owner = THIS_MODULE, |
358 | }, |
359 | }; |
360 | |
361 | module_platform_driver(jz4740_pcm_driver); |
362 | |
363 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); |
364 | MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); |
365 | MODULE_LICENSE("GPL"); |
366 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9