]>
Commit | Line | Data |
---|---|---|
aff0510c CC |
1 | /* |
2 | * File: sound/soc/blackfin/bf5xx-ac97-pcm.c | |
3 | * Author: Cliff Cai <Cliff.Cai@analog.com> | |
4 | * | |
5 | * Created: Tue June 06 2008 | |
6 | * Description: DMA Driver for AC97 sound chip | |
7 | * | |
8 | * Modified: | |
9 | * Copyright 2008 Analog Devices Inc. | |
10 | * | |
11 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License as published by | |
15 | * the Free Software Foundation; either version 2 of the License, or | |
16 | * (at your option) any later version. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, | |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | * GNU General Public License for more details. | |
22 | * | |
23 | * You should have received a copy of the GNU General Public License | |
24 | * along with this program; if not, see the file COPYING, or write | |
25 | * to the Free Software Foundation, Inc., | |
26 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
27 | */ | |
28 | ||
29 | #include <linux/module.h> | |
30 | #include <linux/init.h> | |
31 | #include <linux/platform_device.h> | |
32 | #include <linux/slab.h> | |
33 | #include <linux/dma-mapping.h> | |
34 | ||
35 | #include <sound/core.h> | |
36 | #include <sound/pcm.h> | |
37 | #include <sound/pcm_params.h> | |
38 | #include <sound/soc.h> | |
39 | ||
40 | #include <asm/dma.h> | |
41 | ||
42 | #include "bf5xx-ac97-pcm.h" | |
43 | #include "bf5xx-ac97.h" | |
44 | #include "bf5xx-sport.h" | |
45 | ||
67f854b9 CC |
46 | static unsigned int ac97_chan_mask[] = { |
47 | SP_FL, /* Mono */ | |
48 | SP_STEREO, /* Stereo */ | |
49 | SP_2DOT1, /* 2.1*/ | |
50 | SP_QUAD,/*Quadraquic*/ | |
51 | SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */ | |
52 | SP_5DOT1, /* 5.1 */ | |
53 | }; | |
54 | ||
55 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) | |
aff0510c CC |
56 | static void bf5xx_mmap_copy(struct snd_pcm_substream *substream, |
57 | snd_pcm_uframes_t count) | |
58 | { | |
59 | struct snd_pcm_runtime *runtime = substream->runtime; | |
60 | struct sport_device *sport = runtime->private_data; | |
67f854b9 | 61 | unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1]; |
aff0510c | 62 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
67f854b9 CC |
63 | bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf + |
64 | sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos * | |
65 | runtime->channels, count, chan_mask); | |
aff0510c CC |
66 | sport->tx_pos += runtime->period_size; |
67 | if (sport->tx_pos >= runtime->buffer_size) | |
68 | sport->tx_pos %= runtime->buffer_size; | |
6b58a821 | 69 | sport->tx_delay_pos = sport->tx_pos; |
aff0510c | 70 | } else { |
67f854b9 CC |
71 | bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf + |
72 | sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos * | |
73 | runtime->channels, count); | |
aff0510c CC |
74 | sport->rx_pos += runtime->period_size; |
75 | if (sport->rx_pos >= runtime->buffer_size) | |
76 | sport->rx_pos %= runtime->buffer_size; | |
77 | } | |
78 | } | |
79 | #endif | |
80 | ||
81 | static void bf5xx_dma_irq(void *data) | |
82 | { | |
83 | struct snd_pcm_substream *pcm = data; | |
67f854b9 | 84 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c | 85 | struct snd_pcm_runtime *runtime = pcm->runtime; |
6b58a821 | 86 | struct sport_device *sport = runtime->private_data; |
aff0510c | 87 | bf5xx_mmap_copy(pcm, runtime->period_size); |
6b58a821 CC |
88 | if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
89 | if (sport->once == 0) { | |
90 | snd_pcm_period_elapsed(pcm); | |
91 | bf5xx_mmap_copy(pcm, runtime->period_size); | |
92 | sport->once = 1; | |
93 | } | |
94 | } | |
aff0510c CC |
95 | #endif |
96 | snd_pcm_period_elapsed(pcm); | |
97 | } | |
98 | ||
99 | /* The memory size for pure pcm data is 128*1024 = 0x20000 bytes. | |
100 | * The total rx/tx buffer is for ac97 frame to hold all pcm data | |
101 | * is 0x20000 * sizeof(struct ac97_frame) / 4. | |
102 | */ | |
aff0510c CC |
103 | static const struct snd_pcm_hardware bf5xx_pcm_hardware = { |
104 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
27b9be5a | 105 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
106 | SNDRV_PCM_INFO_MMAP | |
107 | SNDRV_PCM_INFO_MMAP_VALID | | |
aff0510c | 108 | #endif |
27b9be5a BW |
109 | SNDRV_PCM_INFO_BLOCK_TRANSFER, |
110 | ||
aff0510c CC |
111 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
112 | .period_bytes_min = 32, | |
113 | .period_bytes_max = 0x10000, | |
114 | .periods_min = 1, | |
115 | .periods_max = PAGE_SIZE/32, | |
116 | .buffer_bytes_max = 0x20000, /* 128 kbytes */ | |
117 | .fifo_size = 16, | |
118 | }; | |
119 | ||
120 | static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, | |
121 | struct snd_pcm_hw_params *params) | |
122 | { | |
123 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max | |
124 | * sizeof(struct ac97_frame) / 4; | |
125 | ||
126 | snd_pcm_lib_malloc_pages(substream, size); | |
127 | ||
128 | return 0; | |
129 | } | |
130 | ||
131 | static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) | |
132 | { | |
67f854b9 | 133 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
6b58a821 | 134 | struct snd_pcm_runtime *runtime = substream->runtime; |
67f854b9 | 135 | struct sport_device *sport = runtime->private_data; |
6b58a821 | 136 | |
67f854b9 CC |
137 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
138 | sport->once = 0; | |
139 | if (runtime->dma_area) | |
140 | memset(runtime->dma_area, 0, runtime->buffer_size); | |
141 | memset(sport->tx_dma_buf, 0, runtime->buffer_size * | |
142 | sizeof(struct ac97_frame)); | |
143 | } else | |
144 | memset(sport->rx_dma_buf, 0, runtime->buffer_size * | |
145 | sizeof(struct ac97_frame)); | |
146 | #endif | |
aff0510c CC |
147 | snd_pcm_lib_free_pages(substream); |
148 | return 0; | |
149 | } | |
150 | ||
151 | static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) | |
152 | { | |
153 | struct snd_pcm_runtime *runtime = substream->runtime; | |
154 | struct sport_device *sport = runtime->private_data; | |
155 | ||
156 | /* An intermediate buffer is introduced for implementing mmap for | |
157 | * SPORT working in TMD mode(include AC97). | |
158 | */ | |
67f854b9 | 159 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c | 160 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
aff0510c CC |
161 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); |
162 | sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods, | |
163 | runtime->period_size * sizeof(struct ac97_frame)); | |
164 | } else { | |
aff0510c CC |
165 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); |
166 | sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods, | |
167 | runtime->period_size * sizeof(struct ac97_frame)); | |
168 | } | |
169 | #else | |
170 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
171 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); | |
172 | sport_config_tx_dma(sport, runtime->dma_area, runtime->periods, | |
173 | runtime->period_size * sizeof(struct ac97_frame)); | |
174 | } else { | |
175 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); | |
176 | sport_config_rx_dma(sport, runtime->dma_area, runtime->periods, | |
177 | runtime->period_size * sizeof(struct ac97_frame)); | |
178 | } | |
179 | #endif | |
180 | return 0; | |
181 | } | |
182 | ||
183 | static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | |
184 | { | |
185 | struct snd_pcm_runtime *runtime = substream->runtime; | |
186 | struct sport_device *sport = runtime->private_data; | |
187 | int ret = 0; | |
188 | ||
189 | pr_debug("%s enter\n", __func__); | |
190 | switch (cmd) { | |
191 | case SNDRV_PCM_TRIGGER_START: | |
6b58a821 | 192 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
a89e611a | 193 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
6b58a821 | 194 | bf5xx_mmap_copy(substream, runtime->period_size); |
6b58a821 | 195 | sport->tx_delay_pos = 0; |
a89e611a | 196 | #endif |
aff0510c | 197 | sport_tx_start(sport); |
67f854b9 | 198 | } else |
aff0510c CC |
199 | sport_rx_start(sport); |
200 | break; | |
201 | case SNDRV_PCM_TRIGGER_STOP: | |
202 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
203 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
204 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
67f854b9 | 205 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
206 | sport->tx_pos = 0; |
207 | #endif | |
208 | sport_tx_stop(sport); | |
209 | } else { | |
67f854b9 | 210 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
211 | sport->rx_pos = 0; |
212 | #endif | |
213 | sport_rx_stop(sport); | |
214 | } | |
215 | break; | |
216 | default: | |
217 | ret = -EINVAL; | |
218 | } | |
219 | return ret; | |
220 | } | |
221 | ||
222 | static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) | |
223 | { | |
224 | struct snd_pcm_runtime *runtime = substream->runtime; | |
225 | struct sport_device *sport = runtime->private_data; | |
226 | unsigned int curr; | |
227 | ||
67f854b9 | 228 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c | 229 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
6b58a821 | 230 | curr = sport->tx_delay_pos; |
aff0510c CC |
231 | else |
232 | curr = sport->rx_pos; | |
233 | #else | |
234 | ||
235 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
236 | curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame); | |
237 | else | |
238 | curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame); | |
239 | ||
240 | #endif | |
241 | return curr; | |
242 | } | |
243 | ||
244 | static int bf5xx_pcm_open(struct snd_pcm_substream *substream) | |
245 | { | |
246 | struct snd_pcm_runtime *runtime = substream->runtime; | |
247 | int ret; | |
248 | ||
249 | pr_debug("%s enter\n", __func__); | |
250 | snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); | |
251 | ||
252 | ret = snd_pcm_hw_constraint_integer(runtime, | |
253 | SNDRV_PCM_HW_PARAM_PERIODS); | |
254 | if (ret < 0) | |
255 | goto out; | |
256 | ||
257 | if (sport_handle != NULL) | |
258 | runtime->private_data = sport_handle; | |
259 | else { | |
260 | pr_err("sport_handle is NULL\n"); | |
261 | return -1; | |
262 | } | |
263 | return 0; | |
264 | ||
265 | out: | |
266 | return ret; | |
267 | } | |
268 | ||
67f854b9 | 269 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
270 | static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, |
271 | struct vm_area_struct *vma) | |
272 | { | |
273 | struct snd_pcm_runtime *runtime = substream->runtime; | |
274 | size_t size = vma->vm_end - vma->vm_start; | |
275 | vma->vm_start = (unsigned long)runtime->dma_area; | |
276 | vma->vm_end = vma->vm_start + size; | |
277 | vma->vm_flags |= VM_SHARED; | |
278 | return 0 ; | |
279 | } | |
280 | #else | |
281 | static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, | |
282 | snd_pcm_uframes_t pos, | |
283 | void __user *buf, snd_pcm_uframes_t count) | |
284 | { | |
285 | struct snd_pcm_runtime *runtime = substream->runtime; | |
a89e611a | 286 | unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1]; |
aff0510c CC |
287 | pr_debug("%s copy pos:0x%lx count:0x%lx\n", |
288 | substream->stream ? "Capture" : "Playback", pos, count); | |
289 | ||
290 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
67f854b9 CC |
291 | bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos, |
292 | (__u16 *)buf, count, chan_mask); | |
aff0510c | 293 | else |
67f854b9 CC |
294 | bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos, |
295 | (__u16 *)buf, count); | |
aff0510c CC |
296 | return 0; |
297 | } | |
298 | #endif | |
299 | ||
b2a19d02 | 300 | static struct snd_pcm_ops bf5xx_pcm_ac97_ops = { |
aff0510c CC |
301 | .open = bf5xx_pcm_open, |
302 | .ioctl = snd_pcm_lib_ioctl, | |
303 | .hw_params = bf5xx_pcm_hw_params, | |
304 | .hw_free = bf5xx_pcm_hw_free, | |
305 | .prepare = bf5xx_pcm_prepare, | |
306 | .trigger = bf5xx_pcm_trigger, | |
307 | .pointer = bf5xx_pcm_pointer, | |
67f854b9 | 308 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
309 | .mmap = bf5xx_pcm_mmap, |
310 | #else | |
311 | .copy = bf5xx_pcm_copy, | |
312 | #endif | |
313 | }; | |
314 | ||
315 | static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | |
316 | { | |
317 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | |
318 | struct snd_dma_buffer *buf = &substream->dma_buffer; | |
319 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max | |
320 | * sizeof(struct ac97_frame) / 4; | |
321 | ||
322 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | |
323 | buf->dev.dev = pcm->card->dev; | |
324 | buf->private_data = NULL; | |
325 | buf->area = dma_alloc_coherent(pcm->card->dev, size, | |
326 | &buf->addr, GFP_KERNEL); | |
327 | if (!buf->area) { | |
328 | pr_err("Failed to allocate dma memory\n"); | |
329 | pr_err("Please increase uncached DMA memory region\n"); | |
330 | return -ENOMEM; | |
331 | } | |
332 | buf->bytes = size; | |
333 | ||
334 | pr_debug("%s, area:%p, size:0x%08lx\n", __func__, | |
335 | buf->area, buf->bytes); | |
336 | ||
337 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) | |
338 | sport_handle->tx_buf = buf->area; | |
339 | else | |
340 | sport_handle->rx_buf = buf->area; | |
341 | ||
342 | /* | |
343 | * Need to allocate local buffer when enable | |
344 | * MMAP for SPORT working in TMD mode (include AC97). | |
345 | */ | |
67f854b9 | 346 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
347 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
348 | if (!sport_handle->tx_dma_buf) { | |
349 | sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \ | |
350 | size, &sport_handle->tx_dma_phy, GFP_KERNEL); | |
351 | if (!sport_handle->tx_dma_buf) { | |
2f1ff661 | 352 | pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n"); |
aff0510c CC |
353 | return -ENOMEM; |
354 | } else | |
355 | memset(sport_handle->tx_dma_buf, 0, size); | |
356 | } else | |
357 | memset(sport_handle->tx_dma_buf, 0, size); | |
358 | } else { | |
359 | if (!sport_handle->rx_dma_buf) { | |
360 | sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \ | |
361 | size, &sport_handle->rx_dma_phy, GFP_KERNEL); | |
362 | if (!sport_handle->rx_dma_buf) { | |
2f1ff661 | 363 | pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n"); |
aff0510c CC |
364 | return -ENOMEM; |
365 | } else | |
366 | memset(sport_handle->rx_dma_buf, 0, size); | |
367 | } else | |
368 | memset(sport_handle->rx_dma_buf, 0, size); | |
369 | } | |
370 | #endif | |
371 | return 0; | |
372 | } | |
373 | ||
374 | static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | |
375 | { | |
376 | struct snd_pcm_substream *substream; | |
377 | struct snd_dma_buffer *buf; | |
378 | int stream; | |
67f854b9 | 379 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
380 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max * |
381 | sizeof(struct ac97_frame) / 4; | |
382 | #endif | |
383 | for (stream = 0; stream < 2; stream++) { | |
384 | substream = pcm->streams[stream].substream; | |
385 | if (!substream) | |
386 | continue; | |
387 | ||
388 | buf = &substream->dma_buffer; | |
389 | if (!buf->area) | |
390 | continue; | |
391 | dma_free_coherent(NULL, buf->bytes, buf->area, 0); | |
392 | buf->area = NULL; | |
67f854b9 | 393 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
394 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
395 | if (sport_handle->tx_dma_buf) | |
396 | dma_free_coherent(NULL, size, \ | |
397 | sport_handle->tx_dma_buf, 0); | |
398 | sport_handle->tx_dma_buf = NULL; | |
399 | } else { | |
400 | ||
401 | if (sport_handle->rx_dma_buf) | |
402 | dma_free_coherent(NULL, size, \ | |
403 | sport_handle->rx_dma_buf, 0); | |
404 | sport_handle->rx_dma_buf = NULL; | |
405 | } | |
406 | #endif | |
407 | } | |
408 | if (sport_handle) | |
409 | sport_done(sport_handle); | |
410 | } | |
411 | ||
284901a9 | 412 | static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); |
aff0510c CC |
413 | |
414 | int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai, | |
415 | struct snd_pcm *pcm) | |
416 | { | |
417 | int ret = 0; | |
418 | ||
419 | pr_debug("%s enter\n", __func__); | |
420 | if (!card->dev->dma_mask) | |
421 | card->dev->dma_mask = &bf5xx_pcm_dmamask; | |
422 | if (!card->dev->coherent_dma_mask) | |
284901a9 | 423 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); |
aff0510c CC |
424 | |
425 | if (dai->playback.channels_min) { | |
426 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | |
427 | SNDRV_PCM_STREAM_PLAYBACK); | |
428 | if (ret) | |
429 | goto out; | |
430 | } | |
431 | ||
432 | if (dai->capture.channels_min) { | |
433 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | |
434 | SNDRV_PCM_STREAM_CAPTURE); | |
435 | if (ret) | |
436 | goto out; | |
437 | } | |
438 | out: | |
439 | return ret; | |
440 | } | |
441 | ||
442 | struct snd_soc_platform bf5xx_ac97_soc_platform = { | |
443 | .name = "bf5xx-audio", | |
444 | .pcm_ops = &bf5xx_pcm_ac97_ops, | |
445 | .pcm_new = bf5xx_pcm_ac97_new, | |
446 | .pcm_free = bf5xx_pcm_free_dma_buffers, | |
447 | }; | |
448 | EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform); | |
449 | ||
c9b3a40f | 450 | static int __init bfin_ac97_init(void) |
958e792c MB |
451 | { |
452 | return snd_soc_register_platform(&bf5xx_ac97_soc_platform); | |
453 | } | |
454 | module_init(bfin_ac97_init); | |
455 | ||
456 | static void __exit bfin_ac97_exit(void) | |
457 | { | |
458 | snd_soc_unregister_platform(&bf5xx_ac97_soc_platform); | |
459 | } | |
460 | module_exit(bfin_ac97_exit); | |
461 | ||
aff0510c CC |
462 | MODULE_AUTHOR("Cliff Cai"); |
463 | MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); | |
464 | MODULE_LICENSE("GPL"); |