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