• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt-6.x.4708/linux/linux-2.6.36/sound/soc/jz4740/
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	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
92static 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
104static 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->dai->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
143static 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
156static 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
168static 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
191static 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
213static 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
229static 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
239static 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
247static 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
259static 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
279static 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
300static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
301
302int 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->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->capture.channels_min) {
321		ret = jz4740_pcm_preallocate_dma_buffer(pcm,
322			SNDRV_PCM_STREAM_CAPTURE);
323		if (ret)
324			goto err;
325	}
326
327err:
328	return ret;
329}
330
331struct snd_soc_platform jz4740_soc_platform = {
332		.name		= "jz4740-pcm",
333		.pcm_ops	= &jz4740_pcm_ops,
334		.pcm_new	= jz4740_pcm_new,
335		.pcm_free	= jz4740_pcm_free,
336};
337EXPORT_SYMBOL_GPL(jz4740_soc_platform);
338
339static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
340{
341	return snd_soc_register_platform(&jz4740_soc_platform);
342}
343
344static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
345{
346	snd_soc_unregister_platform(&jz4740_soc_platform);
347	return 0;
348}
349
350static struct platform_driver jz4740_pcm_driver = {
351	.probe = jz4740_pcm_probe,
352	.remove = __devexit_p(jz4740_pcm_remove),
353	.driver = {
354		.name = "jz4740-pcm",
355		.owner = THIS_MODULE,
356	},
357};
358
359static int __init jz4740_soc_platform_init(void)
360{
361	return platform_driver_register(&jz4740_pcm_driver);
362}
363module_init(jz4740_soc_platform_init);
364
365static void __exit jz4740_soc_platform_exit(void)
366{
367	return platform_driver_unregister(&jz4740_pcm_driver);
368}
369module_exit(jz4740_soc_platform_exit);
370
371MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
372MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
373MODULE_LICENSE("GPL");
374