Root/sound/soc/jz4740/jz4740-pcm.c

Source at commit 4bbb1eea9c3c814b96eb9987571274d3c9e1f686 created 8 years 7 months ago.
By Maarten ter Huurne, MIPS: JZ4740: Use round robin DMA channel priority mode.
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
32struct 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 */
44static 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
62static 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
85static 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
97static 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
138static 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
151static 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
163static 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
186static 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
208static 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
232static 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
242static 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
250static 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
262static 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
282static 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
303static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
304
305static 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
331err:
332    return ret;
333}
334
335static 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
341static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
342{
343    return snd_soc_register_platform(&pdev->dev, &jz4740_soc_platform);
344}
345
346static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
347{
348    snd_soc_unregister_platform(&pdev->dev);
349    return 0;
350}
351
352static 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
361module_platform_driver(jz4740_pcm_driver);
362
363MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
364MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
365MODULE_LICENSE("GPL");
366

Archive Download this file



interactive