]>
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 | */ | |
67f854b9 | 103 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
104 | static const struct snd_pcm_hardware bf5xx_pcm_hardware = { |
105 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
106 | SNDRV_PCM_INFO_MMAP | | |
107 | SNDRV_PCM_INFO_MMAP_VALID | | |
108 | SNDRV_PCM_INFO_BLOCK_TRANSFER, | |
109 | #else | |
110 | static const struct snd_pcm_hardware bf5xx_pcm_hardware = { | |
111 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
112 | SNDRV_PCM_INFO_BLOCK_TRANSFER, | |
113 | #endif | |
114 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | |
115 | .period_bytes_min = 32, | |
116 | .period_bytes_max = 0x10000, | |
117 | .periods_min = 1, | |
118 | .periods_max = PAGE_SIZE/32, | |
119 | .buffer_bytes_max = 0x20000, /* 128 kbytes */ | |
120 | .fifo_size = 16, | |
121 | }; | |
122 | ||
123 | static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, | |
124 | struct snd_pcm_hw_params *params) | |
125 | { | |
126 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max | |
127 | * sizeof(struct ac97_frame) / 4; | |
128 | ||
129 | snd_pcm_lib_malloc_pages(substream, size); | |
130 | ||
131 | return 0; | |
132 | } | |
133 | ||
134 | static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) | |
135 | { | |
67f854b9 | 136 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
6b58a821 | 137 | struct snd_pcm_runtime *runtime = substream->runtime; |
67f854b9 | 138 | struct sport_device *sport = runtime->private_data; |
6b58a821 | 139 | |
67f854b9 CC |
140 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
141 | sport->once = 0; | |
142 | if (runtime->dma_area) | |
143 | memset(runtime->dma_area, 0, runtime->buffer_size); | |
144 | memset(sport->tx_dma_buf, 0, runtime->buffer_size * | |
145 | sizeof(struct ac97_frame)); | |
146 | } else | |
147 | memset(sport->rx_dma_buf, 0, runtime->buffer_size * | |
148 | sizeof(struct ac97_frame)); | |
149 | #endif | |
aff0510c CC |
150 | snd_pcm_lib_free_pages(substream); |
151 | return 0; | |
152 | } | |
153 | ||
154 | static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) | |
155 | { | |
156 | struct snd_pcm_runtime *runtime = substream->runtime; | |
157 | struct sport_device *sport = runtime->private_data; | |
158 | ||
159 | /* An intermediate buffer is introduced for implementing mmap for | |
160 | * SPORT working in TMD mode(include AC97). | |
161 | */ | |
67f854b9 | 162 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c | 163 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
aff0510c CC |
164 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); |
165 | sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods, | |
166 | runtime->period_size * sizeof(struct ac97_frame)); | |
167 | } else { | |
aff0510c CC |
168 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); |
169 | sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods, | |
170 | runtime->period_size * sizeof(struct ac97_frame)); | |
171 | } | |
172 | #else | |
173 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
174 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); | |
175 | sport_config_tx_dma(sport, runtime->dma_area, runtime->periods, | |
176 | runtime->period_size * sizeof(struct ac97_frame)); | |
177 | } else { | |
178 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); | |
179 | sport_config_rx_dma(sport, runtime->dma_area, runtime->periods, | |
180 | runtime->period_size * sizeof(struct ac97_frame)); | |
181 | } | |
182 | #endif | |
183 | return 0; | |
184 | } | |
185 | ||
186 | static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | |
187 | { | |
188 | struct snd_pcm_runtime *runtime = substream->runtime; | |
189 | struct sport_device *sport = runtime->private_data; | |
190 | int ret = 0; | |
191 | ||
192 | pr_debug("%s enter\n", __func__); | |
193 | switch (cmd) { | |
194 | case SNDRV_PCM_TRIGGER_START: | |
6b58a821 CC |
195 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
196 | bf5xx_mmap_copy(substream, runtime->period_size); | |
6b58a821 | 197 | sport->tx_delay_pos = 0; |
aff0510c | 198 | sport_tx_start(sport); |
67f854b9 | 199 | } else |
aff0510c CC |
200 | sport_rx_start(sport); |
201 | break; | |
202 | case SNDRV_PCM_TRIGGER_STOP: | |
203 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
204 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
205 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
67f854b9 | 206 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
207 | sport->tx_pos = 0; |
208 | #endif | |
209 | sport_tx_stop(sport); | |
210 | } else { | |
67f854b9 | 211 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
212 | sport->rx_pos = 0; |
213 | #endif | |
214 | sport_rx_stop(sport); | |
215 | } | |
216 | break; | |
217 | default: | |
218 | ret = -EINVAL; | |
219 | } | |
220 | return ret; | |
221 | } | |
222 | ||
223 | static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) | |
224 | { | |
225 | struct snd_pcm_runtime *runtime = substream->runtime; | |
226 | struct sport_device *sport = runtime->private_data; | |
227 | unsigned int curr; | |
228 | ||
67f854b9 | 229 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c | 230 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
6b58a821 | 231 | curr = sport->tx_delay_pos; |
aff0510c CC |
232 | else |
233 | curr = sport->rx_pos; | |
234 | #else | |
235 | ||
236 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
237 | curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame); | |
238 | else | |
239 | curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame); | |
240 | ||
241 | #endif | |
242 | return curr; | |
243 | } | |
244 | ||
245 | static int bf5xx_pcm_open(struct snd_pcm_substream *substream) | |
246 | { | |
247 | struct snd_pcm_runtime *runtime = substream->runtime; | |
248 | int ret; | |
249 | ||
250 | pr_debug("%s enter\n", __func__); | |
251 | snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); | |
252 | ||
253 | ret = snd_pcm_hw_constraint_integer(runtime, | |
254 | SNDRV_PCM_HW_PARAM_PERIODS); | |
255 | if (ret < 0) | |
256 | goto out; | |
257 | ||
258 | if (sport_handle != NULL) | |
259 | runtime->private_data = sport_handle; | |
260 | else { | |
261 | pr_err("sport_handle is NULL\n"); | |
262 | return -1; | |
263 | } | |
264 | return 0; | |
265 | ||
266 | out: | |
267 | return ret; | |
268 | } | |
269 | ||
6b58a821 CC |
270 | static int bf5xx_pcm_close(struct snd_pcm_substream *substream) |
271 | { | |
272 | struct snd_pcm_runtime *runtime = substream->runtime; | |
273 | struct sport_device *sport = runtime->private_data; | |
274 | ||
275 | pr_debug("%s enter\n", __func__); | |
276 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
277 | sport->once = 0; | |
67f854b9 CC |
278 | memset(sport->tx_dma_buf, 0, runtime->buffer_size * |
279 | sizeof(struct ac97_frame)); | |
6b58a821 | 280 | } else |
67f854b9 CC |
281 | memset(sport->rx_dma_buf, 0, runtime->buffer_size * |
282 | sizeof(struct ac97_frame)); | |
6b58a821 CC |
283 | |
284 | return 0; | |
285 | } | |
286 | ||
67f854b9 | 287 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
288 | static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, |
289 | struct vm_area_struct *vma) | |
290 | { | |
291 | struct snd_pcm_runtime *runtime = substream->runtime; | |
292 | size_t size = vma->vm_end - vma->vm_start; | |
293 | vma->vm_start = (unsigned long)runtime->dma_area; | |
294 | vma->vm_end = vma->vm_start + size; | |
295 | vma->vm_flags |= VM_SHARED; | |
296 | return 0 ; | |
297 | } | |
298 | #else | |
299 | static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, | |
300 | snd_pcm_uframes_t pos, | |
301 | void __user *buf, snd_pcm_uframes_t count) | |
302 | { | |
303 | struct snd_pcm_runtime *runtime = substream->runtime; | |
304 | ||
305 | pr_debug("%s copy pos:0x%lx count:0x%lx\n", | |
306 | substream->stream ? "Capture" : "Playback", pos, count); | |
307 | ||
308 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
67f854b9 CC |
309 | bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos, |
310 | (__u16 *)buf, count, chan_mask); | |
aff0510c | 311 | else |
67f854b9 CC |
312 | bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos, |
313 | (__u16 *)buf, count); | |
aff0510c CC |
314 | return 0; |
315 | } | |
316 | #endif | |
317 | ||
318 | struct snd_pcm_ops bf5xx_pcm_ac97_ops = { | |
319 | .open = bf5xx_pcm_open, | |
6b58a821 | 320 | .close = bf5xx_pcm_close, |
aff0510c CC |
321 | .ioctl = snd_pcm_lib_ioctl, |
322 | .hw_params = bf5xx_pcm_hw_params, | |
323 | .hw_free = bf5xx_pcm_hw_free, | |
324 | .prepare = bf5xx_pcm_prepare, | |
325 | .trigger = bf5xx_pcm_trigger, | |
326 | .pointer = bf5xx_pcm_pointer, | |
67f854b9 | 327 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
328 | .mmap = bf5xx_pcm_mmap, |
329 | #else | |
330 | .copy = bf5xx_pcm_copy, | |
331 | #endif | |
332 | }; | |
333 | ||
334 | static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | |
335 | { | |
336 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | |
337 | struct snd_dma_buffer *buf = &substream->dma_buffer; | |
338 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max | |
339 | * sizeof(struct ac97_frame) / 4; | |
340 | ||
341 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | |
342 | buf->dev.dev = pcm->card->dev; | |
343 | buf->private_data = NULL; | |
344 | buf->area = dma_alloc_coherent(pcm->card->dev, size, | |
345 | &buf->addr, GFP_KERNEL); | |
346 | if (!buf->area) { | |
347 | pr_err("Failed to allocate dma memory\n"); | |
348 | pr_err("Please increase uncached DMA memory region\n"); | |
349 | return -ENOMEM; | |
350 | } | |
351 | buf->bytes = size; | |
352 | ||
353 | pr_debug("%s, area:%p, size:0x%08lx\n", __func__, | |
354 | buf->area, buf->bytes); | |
355 | ||
356 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) | |
357 | sport_handle->tx_buf = buf->area; | |
358 | else | |
359 | sport_handle->rx_buf = buf->area; | |
360 | ||
361 | /* | |
362 | * Need to allocate local buffer when enable | |
363 | * MMAP for SPORT working in TMD mode (include AC97). | |
364 | */ | |
67f854b9 | 365 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
366 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
367 | if (!sport_handle->tx_dma_buf) { | |
368 | sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \ | |
369 | size, &sport_handle->tx_dma_phy, GFP_KERNEL); | |
370 | if (!sport_handle->tx_dma_buf) { | |
371 | pr_err("Failed to allocate memory for tx dma \ | |
372 | buf - Please increase uncached DMA \ | |
373 | memory region\n"); | |
374 | return -ENOMEM; | |
375 | } else | |
376 | memset(sport_handle->tx_dma_buf, 0, size); | |
377 | } else | |
378 | memset(sport_handle->tx_dma_buf, 0, size); | |
379 | } else { | |
380 | if (!sport_handle->rx_dma_buf) { | |
381 | sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \ | |
382 | size, &sport_handle->rx_dma_phy, GFP_KERNEL); | |
383 | if (!sport_handle->rx_dma_buf) { | |
384 | pr_err("Failed to allocate memory for rx dma \ | |
385 | buf - Please increase uncached DMA \ | |
386 | memory region\n"); | |
387 | return -ENOMEM; | |
388 | } else | |
389 | memset(sport_handle->rx_dma_buf, 0, size); | |
390 | } else | |
391 | memset(sport_handle->rx_dma_buf, 0, size); | |
392 | } | |
393 | #endif | |
394 | return 0; | |
395 | } | |
396 | ||
397 | static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | |
398 | { | |
399 | struct snd_pcm_substream *substream; | |
400 | struct snd_dma_buffer *buf; | |
401 | int stream; | |
67f854b9 | 402 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
403 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max * |
404 | sizeof(struct ac97_frame) / 4; | |
405 | #endif | |
406 | for (stream = 0; stream < 2; stream++) { | |
407 | substream = pcm->streams[stream].substream; | |
408 | if (!substream) | |
409 | continue; | |
410 | ||
411 | buf = &substream->dma_buffer; | |
412 | if (!buf->area) | |
413 | continue; | |
414 | dma_free_coherent(NULL, buf->bytes, buf->area, 0); | |
415 | buf->area = NULL; | |
67f854b9 | 416 | #if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) |
aff0510c CC |
417 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
418 | if (sport_handle->tx_dma_buf) | |
419 | dma_free_coherent(NULL, size, \ | |
420 | sport_handle->tx_dma_buf, 0); | |
421 | sport_handle->tx_dma_buf = NULL; | |
422 | } else { | |
423 | ||
424 | if (sport_handle->rx_dma_buf) | |
425 | dma_free_coherent(NULL, size, \ | |
426 | sport_handle->rx_dma_buf, 0); | |
427 | sport_handle->rx_dma_buf = NULL; | |
428 | } | |
429 | #endif | |
430 | } | |
431 | if (sport_handle) | |
432 | sport_done(sport_handle); | |
433 | } | |
434 | ||
435 | static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; | |
436 | ||
437 | int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai, | |
438 | struct snd_pcm *pcm) | |
439 | { | |
440 | int ret = 0; | |
441 | ||
442 | pr_debug("%s enter\n", __func__); | |
443 | if (!card->dev->dma_mask) | |
444 | card->dev->dma_mask = &bf5xx_pcm_dmamask; | |
445 | if (!card->dev->coherent_dma_mask) | |
446 | card->dev->coherent_dma_mask = DMA_32BIT_MASK; | |
447 | ||
448 | if (dai->playback.channels_min) { | |
449 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | |
450 | SNDRV_PCM_STREAM_PLAYBACK); | |
451 | if (ret) | |
452 | goto out; | |
453 | } | |
454 | ||
455 | if (dai->capture.channels_min) { | |
456 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | |
457 | SNDRV_PCM_STREAM_CAPTURE); | |
458 | if (ret) | |
459 | goto out; | |
460 | } | |
461 | out: | |
462 | return ret; | |
463 | } | |
464 | ||
465 | struct snd_soc_platform bf5xx_ac97_soc_platform = { | |
466 | .name = "bf5xx-audio", | |
467 | .pcm_ops = &bf5xx_pcm_ac97_ops, | |
468 | .pcm_new = bf5xx_pcm_ac97_new, | |
469 | .pcm_free = bf5xx_pcm_free_dma_buffers, | |
470 | }; | |
471 | EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform); | |
472 | ||
473 | MODULE_AUTHOR("Cliff Cai"); | |
474 | MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); | |
475 | MODULE_LICENSE("GPL"); |