Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/sound/soc/blackfin/bf5xx-ac97-pcm.c
10817 views
1
/*
2
* File: sound/soc/blackfin/bf5xx-ac97-pcm.c
3
* Author: Cliff Cai <[email protected]>
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/dma-mapping.h>
33
#include <linux/gfp.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
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)
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;
61
unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
62
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
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);
66
sport->tx_pos += runtime->period_size;
67
if (sport->tx_pos >= runtime->buffer_size)
68
sport->tx_pos %= runtime->buffer_size;
69
sport->tx_delay_pos = sport->tx_pos;
70
} else {
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);
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;
84
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
85
struct snd_pcm_runtime *runtime = pcm->runtime;
86
struct sport_device *sport = runtime->private_data;
87
bf5xx_mmap_copy(pcm, runtime->period_size);
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
}
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
*/
103
static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
104
.info = SNDRV_PCM_INFO_INTERLEAVED |
105
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
106
SNDRV_PCM_INFO_MMAP |
107
SNDRV_PCM_INFO_MMAP_VALID |
108
#endif
109
SNDRV_PCM_INFO_BLOCK_TRANSFER,
110
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
{
133
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
134
struct snd_pcm_runtime *runtime = substream->runtime;
135
struct sport_device *sport = runtime->private_data;
136
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
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
*/
159
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
160
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
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 {
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:
192
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
193
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
194
bf5xx_mmap_copy(substream, runtime->period_size);
195
sport->tx_delay_pos = 0;
196
#endif
197
sport_tx_start(sport);
198
} else
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) {
205
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
206
sport->tx_pos = 0;
207
#endif
208
sport_tx_stop(sport);
209
} else {
210
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
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
228
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
229
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
230
curr = sport->tx_delay_pos;
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_soc_pcm_runtime *rtd = substream->private_data;
247
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
248
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
249
struct snd_pcm_runtime *runtime = substream->runtime;
250
int ret;
251
252
pr_debug("%s enter\n", __func__);
253
snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
254
255
ret = snd_pcm_hw_constraint_integer(runtime,
256
SNDRV_PCM_HW_PARAM_PERIODS);
257
if (ret < 0)
258
goto out;
259
260
if (sport_handle != NULL)
261
runtime->private_data = sport_handle;
262
else {
263
pr_err("sport_handle is NULL\n");
264
return -1;
265
}
266
return 0;
267
268
out:
269
return ret;
270
}
271
272
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
273
static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
274
struct vm_area_struct *vma)
275
{
276
struct snd_pcm_runtime *runtime = substream->runtime;
277
size_t size = vma->vm_end - vma->vm_start;
278
vma->vm_start = (unsigned long)runtime->dma_area;
279
vma->vm_end = vma->vm_start + size;
280
vma->vm_flags |= VM_SHARED;
281
return 0 ;
282
}
283
#else
284
static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
285
snd_pcm_uframes_t pos,
286
void __user *buf, snd_pcm_uframes_t count)
287
{
288
struct snd_pcm_runtime *runtime = substream->runtime;
289
unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
290
pr_debug("%s copy pos:0x%lx count:0x%lx\n",
291
substream->stream ? "Capture" : "Playback", pos, count);
292
293
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
294
bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos,
295
(__u16 *)buf, count, chan_mask);
296
else
297
bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos,
298
(__u16 *)buf, count);
299
return 0;
300
}
301
#endif
302
303
static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
304
.open = bf5xx_pcm_open,
305
.ioctl = snd_pcm_lib_ioctl,
306
.hw_params = bf5xx_pcm_hw_params,
307
.hw_free = bf5xx_pcm_hw_free,
308
.prepare = bf5xx_pcm_prepare,
309
.trigger = bf5xx_pcm_trigger,
310
.pointer = bf5xx_pcm_pointer,
311
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
312
.mmap = bf5xx_pcm_mmap,
313
#else
314
.copy = bf5xx_pcm_copy,
315
#endif
316
};
317
318
static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
319
{
320
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
321
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
322
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
323
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
324
struct snd_dma_buffer *buf = &substream->dma_buffer;
325
size_t size = bf5xx_pcm_hardware.buffer_bytes_max
326
* sizeof(struct ac97_frame) / 4;
327
328
buf->dev.type = SNDRV_DMA_TYPE_DEV;
329
buf->dev.dev = pcm->card->dev;
330
buf->private_data = NULL;
331
buf->area = dma_alloc_coherent(pcm->card->dev, size,
332
&buf->addr, GFP_KERNEL);
333
if (!buf->area) {
334
pr_err("Failed to allocate dma memory\n");
335
pr_err("Please increase uncached DMA memory region\n");
336
return -ENOMEM;
337
}
338
buf->bytes = size;
339
340
pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
341
buf->area, buf->bytes);
342
343
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
344
sport_handle->tx_buf = buf->area;
345
else
346
sport_handle->rx_buf = buf->area;
347
348
/*
349
* Need to allocate local buffer when enable
350
* MMAP for SPORT working in TMD mode (include AC97).
351
*/
352
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
353
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
354
if (!sport_handle->tx_dma_buf) {
355
sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
356
size, &sport_handle->tx_dma_phy, GFP_KERNEL);
357
if (!sport_handle->tx_dma_buf) {
358
pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");
359
return -ENOMEM;
360
} else
361
memset(sport_handle->tx_dma_buf, 0, size);
362
} else
363
memset(sport_handle->tx_dma_buf, 0, size);
364
} else {
365
if (!sport_handle->rx_dma_buf) {
366
sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
367
size, &sport_handle->rx_dma_phy, GFP_KERNEL);
368
if (!sport_handle->rx_dma_buf) {
369
pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");
370
return -ENOMEM;
371
} else
372
memset(sport_handle->rx_dma_buf, 0, size);
373
} else
374
memset(sport_handle->rx_dma_buf, 0, size);
375
}
376
#endif
377
return 0;
378
}
379
380
static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
381
{
382
struct snd_pcm_substream *substream;
383
struct snd_dma_buffer *buf;
384
int stream;
385
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
386
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
387
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
388
struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
389
size_t size = bf5xx_pcm_hardware.buffer_bytes_max *
390
sizeof(struct ac97_frame) / 4;
391
#endif
392
for (stream = 0; stream < 2; stream++) {
393
substream = pcm->streams[stream].substream;
394
if (!substream)
395
continue;
396
397
buf = &substream->dma_buffer;
398
if (!buf->area)
399
continue;
400
dma_free_coherent(NULL, buf->bytes, buf->area, 0);
401
buf->area = NULL;
402
#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
403
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
404
if (sport_handle->tx_dma_buf)
405
dma_free_coherent(NULL, size, \
406
sport_handle->tx_dma_buf, 0);
407
sport_handle->tx_dma_buf = NULL;
408
} else {
409
410
if (sport_handle->rx_dma_buf)
411
dma_free_coherent(NULL, size, \
412
sport_handle->rx_dma_buf, 0);
413
sport_handle->rx_dma_buf = NULL;
414
}
415
#endif
416
}
417
}
418
419
static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
420
421
int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,
422
struct snd_pcm *pcm)
423
{
424
int ret = 0;
425
426
pr_debug("%s enter\n", __func__);
427
if (!card->dev->dma_mask)
428
card->dev->dma_mask = &bf5xx_pcm_dmamask;
429
if (!card->dev->coherent_dma_mask)
430
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
431
432
if (dai->driver->playback.channels_min) {
433
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
434
SNDRV_PCM_STREAM_PLAYBACK);
435
if (ret)
436
goto out;
437
}
438
439
if (dai->driver->capture.channels_min) {
440
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
441
SNDRV_PCM_STREAM_CAPTURE);
442
if (ret)
443
goto out;
444
}
445
out:
446
return ret;
447
}
448
449
static struct snd_soc_platform_driver bf5xx_ac97_soc_platform = {
450
.ops = &bf5xx_pcm_ac97_ops,
451
.pcm_new = bf5xx_pcm_ac97_new,
452
.pcm_free = bf5xx_pcm_free_dma_buffers,
453
};
454
455
static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
456
{
457
return snd_soc_register_platform(&pdev->dev, &bf5xx_ac97_soc_platform);
458
}
459
460
static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
461
{
462
snd_soc_unregister_platform(&pdev->dev);
463
return 0;
464
}
465
466
static struct platform_driver bf5xx_pcm_driver = {
467
.driver = {
468
.name = "bfin-ac97-pcm-audio",
469
.owner = THIS_MODULE,
470
},
471
472
.probe = bf5xx_soc_platform_probe,
473
.remove = __devexit_p(bf5xx_soc_platform_remove),
474
};
475
476
static int __init snd_bf5xx_pcm_init(void)
477
{
478
return platform_driver_register(&bf5xx_pcm_driver);
479
}
480
module_init(snd_bf5xx_pcm_init);
481
482
static void __exit snd_bf5xx_pcm_exit(void)
483
{
484
platform_driver_unregister(&bf5xx_pcm_driver);
485
}
486
module_exit(snd_bf5xx_pcm_exit);
487
488
MODULE_AUTHOR("Cliff Cai");
489
MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");
490
MODULE_LICENSE("GPL");
491
492