Root/target/linux/xburst/files-2.6.32/sound/soc/jz4740/jz4740-pcm.c

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/kernel.h>
16#include <linux/module.h>
17#include <linux/init.h>
18#include <linux/interrupt.h>
19#include <linux/dma-mapping.h>
20
21#include <sound/core.h>
22#include <sound/pcm.h>
23#include <sound/pcm_params.h>
24#include <sound/soc.h>
25
26#include <asm/mach-jz4740/dma.h>
27#include "jz4740-pcm.h"
28
29struct jz4740_runtime_data {
30    unsigned int dma_period;
31    dma_addr_t dma_start;
32    dma_addr_t dma_pos;
33    dma_addr_t dma_end;
34
35    struct jz4740_dma_chan *dma;
36
37    dma_addr_t fifo_addr;
38};
39
40/* identify hardware playback capabilities */
41static const struct snd_pcm_hardware jz4740_pcm_hardware = {
42    .info = SNDRV_PCM_INFO_MMAP |
43            SNDRV_PCM_INFO_MMAP_VALID |
44            SNDRV_PCM_INFO_INTERLEAVED |
45            SNDRV_PCM_INFO_BLOCK_TRANSFER,
46    .formats = SNDRV_PCM_FMTBIT_S16_LE |
47               SNDRV_PCM_FMTBIT_S8,
48    .rates = SNDRV_PCM_RATE_8000_48000,
49    .channels_min = 1,
50    .channels_max = 2,
51    .period_bytes_min = 32,
52    .period_bytes_max = 2 * PAGE_SIZE,
53    .periods_min = 2,
54    .periods_max = 128,
55    .buffer_bytes_max = 128 * 2 * PAGE_SIZE,
56    .fifo_size = 32,
57};
58
59static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd, int stream)
60{
61    unsigned int count;
62
63    if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
64        count = prtd->dma_end - prtd->dma_pos;
65    else
66        count = prtd->dma_period;
67
68    jz4740_dma_disable(prtd->dma);
69
70    if (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, count);
79
80    jz4740_dma_enable(prtd->dma);
81
82    prtd->dma_pos += prtd->dma_period;
83    if (prtd->dma_pos >= prtd->dma_end)
84        prtd->dma_pos = prtd->dma_start;
85}
86
87static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
88                                         void *dev_id)
89{
90    struct snd_pcm_substream *substream = dev_id;
91    struct snd_pcm_runtime *runtime = substream->runtime;
92    struct jz4740_runtime_data *prtd = runtime->private_data;
93
94    snd_pcm_period_elapsed(substream);
95
96    jz4740_pcm_start_transfer(prtd, substream->stream);
97}
98
99static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
100    struct snd_pcm_hw_params *params)
101{
102    struct snd_pcm_runtime *runtime = substream->runtime;
103    struct jz4740_runtime_data *prtd = runtime->private_data;
104    struct snd_soc_pcm_runtime *rtd = substream->private_data;
105    struct jz4740_pcm_config *config;
106
107    config = rtd->dai->cpu_dai->dma_data;
108    if (!prtd->dma) {
109        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
110            prtd->dma = jz4740_dma_request(substream, "PCM Playback");
111        else
112            prtd->dma = jz4740_dma_request(substream, "PCM Capture");
113    }
114
115    if (!prtd->dma)
116        return -EBUSY;
117
118    jz4740_dma_configure(prtd->dma, config->dma_config);
119    prtd->fifo_addr = config->fifo_addr;
120
121    jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
122
123    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
124    runtime->dma_bytes = params_buffer_bytes(params);
125
126    prtd->dma_period = params_period_bytes(params);
127    prtd->dma_start = runtime->dma_addr;
128    prtd->dma_pos = prtd->dma_start;
129    prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
130
131    return 0;
132}
133
134static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
135{
136    struct jz4740_runtime_data *prtd = substream->runtime->private_data;
137
138    snd_pcm_set_runtime_buffer(substream, NULL);
139    if (prtd->dma) {
140        jz4740_dma_free(prtd->dma);
141        prtd->dma = NULL;
142    }
143
144    return 0;
145}
146
147static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
148{
149    struct jz4740_runtime_data *prtd = substream->runtime->private_data;
150    int ret = 0;
151
152    if (!prtd->dma)
153         return 0;
154
155    prtd->dma_pos = prtd->dma_start;
156
157    return ret;
158}
159
160static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
161{
162    struct snd_pcm_runtime *runtime = substream->runtime;
163    struct jz4740_runtime_data *prtd = runtime->private_data;
164
165    int ret = 0;
166
167    switch (cmd) {
168    case SNDRV_PCM_TRIGGER_START:
169    case SNDRV_PCM_TRIGGER_RESUME:
170    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
171        jz4740_pcm_start_transfer(prtd, substream->stream);
172        break;
173    case SNDRV_PCM_TRIGGER_STOP:
174    case SNDRV_PCM_TRIGGER_SUSPEND:
175    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
176        jz4740_dma_disable(prtd->dma);
177        break;
178    default:
179        ret = -EINVAL;
180    }
181
182    return ret;
183}
184
185static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
186{
187    struct snd_pcm_runtime *runtime = substream->runtime;
188    struct jz4740_runtime_data *prtd = runtime->private_data;
189    unsigned long count, pos;
190    snd_pcm_uframes_t offset;
191    struct jz4740_dma_chan *dma = prtd->dma;
192
193    count = jz4740_dma_get_residue(dma);
194    if (prtd->dma_pos == prtd->dma_start)
195        pos = prtd->dma_end - prtd->dma_start - count;
196    else
197        pos = prtd->dma_pos - prtd->dma_start - count;
198
199    offset = bytes_to_frames(runtime, pos);
200    if (offset >= runtime->buffer_size)
201        offset = 0;
202
203    return offset;
204}
205
206static int jz4740_pcm_open(struct snd_pcm_substream *substream)
207{
208    struct snd_pcm_runtime *runtime = substream->runtime;
209    struct jz4740_runtime_data *prtd;
210
211    snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
212    prtd = kzalloc(sizeof(struct jz4740_runtime_data), GFP_KERNEL);
213
214    if (prtd == NULL)
215        return -ENOMEM;
216
217    runtime->private_data = prtd;
218    return 0;
219}
220
221static int jz4740_pcm_close(struct snd_pcm_substream *substream)
222{
223    struct snd_pcm_runtime *runtime = substream->runtime;
224    struct jz4740_runtime_data *prtd = runtime->private_data;
225
226    kfree(prtd);
227
228    return 0;
229}
230
231static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
232               struct vm_area_struct *vma)
233{
234    return remap_pfn_range(vma, vma->vm_start,
235               substream->dma_buffer.addr >> PAGE_SHIFT,
236               vma->vm_end - vma->vm_start, vma->vm_page_prot);
237}
238
239static struct snd_pcm_ops jz4740_pcm_ops = {
240    .open = jz4740_pcm_open,
241    .close = jz4740_pcm_close,
242    .ioctl = snd_pcm_lib_ioctl,
243    .hw_params = jz4740_pcm_hw_params,
244    .hw_free = jz4740_pcm_hw_free,
245    .prepare = jz4740_pcm_prepare,
246    .trigger = jz4740_pcm_trigger,
247    .pointer = jz4740_pcm_pointer,
248    .mmap = jz4740_pcm_mmap,
249};
250
251static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
252{
253    struct snd_pcm_substream *substream = pcm->streams[stream].substream;
254    struct snd_dma_buffer *buf = &substream->dma_buffer;
255    size_t size = jz4740_pcm_hardware.buffer_bytes_max;
256
257    buf->dev.type = SNDRV_DMA_TYPE_DEV;
258    buf->dev.dev = pcm->card->dev;
259    buf->private_data = NULL;
260
261    buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
262                      &buf->addr, GFP_KERNEL);
263    if (!buf->area)
264        return -ENOMEM;
265
266    buf->bytes = size;
267
268    return 0;
269}
270
271static void jz4740_pcm_free(struct snd_pcm *pcm)
272{
273    struct snd_pcm_substream *substream;
274    struct snd_dma_buffer *buf;
275    int stream;
276
277    for (stream = 0; stream < 2; stream++) {
278        substream = pcm->streams[stream].substream;
279        if (!substream)
280            continue;
281
282        buf = &substream->dma_buffer;
283        if (!buf->area)
284            continue;
285
286        dma_free_noncoherent(pcm->card->dev, buf->bytes,
287          buf->area, buf->addr);
288        buf->area = NULL;
289    }
290}
291
292static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
293
294int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
295    struct snd_pcm *pcm)
296{
297    int ret = 0;
298
299    if (!card->dev->dma_mask)
300        card->dev->dma_mask = &jz4740_pcm_dmamask;
301
302    if (!card->dev->coherent_dma_mask)
303        card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
304
305    if (dai->playback.channels_min) {
306        ret = jz4740_pcm_preallocate_dma_buffer(pcm,
307            SNDRV_PCM_STREAM_PLAYBACK);
308        if (ret)
309            goto err;
310    }
311
312    if (dai->capture.channels_min) {
313        ret = jz4740_pcm_preallocate_dma_buffer(pcm,
314            SNDRV_PCM_STREAM_CAPTURE);
315        if (ret)
316            goto err;
317    }
318
319err:
320    return ret;
321}
322
323struct snd_soc_platform jz4740_soc_platform = {
324    .name = "jz4740-pcm",
325    .pcm_ops = &jz4740_pcm_ops,
326    .pcm_new = jz4740_pcm_new,
327    .pcm_free = jz4740_pcm_free,
328};
329EXPORT_SYMBOL_GPL(jz4740_soc_platform);
330
331static int __init jz4740_soc_platform_init(void)
332{
333    return snd_soc_register_platform(&jz4740_soc_platform);
334}
335module_init(jz4740_soc_platform_init);
336
337static void __exit jz4740_soc_platform_exit(void)
338{
339    snd_soc_unregister_platform(&jz4740_soc_platform);
340}
341module_exit(jz4740_soc_platform_exit);
342
343MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
344MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
345MODULE_LICENSE("GPL");
346

Archive Download this file



interactive