• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/sound/soc/omap/
1/*
2 * omap-pcm.c  --  ALSA PCM interface for the OMAP SoC
3 *
4 * Copyright (C) 2008 Nokia Corporation
5 *
6 * Contact: Jarkko Nikula <jhnikula@gmail.com>
7 *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * version 2 as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25#include <linux/dma-mapping.h>
26#include <linux/slab.h>
27#include <sound/core.h>
28#include <sound/pcm.h>
29#include <sound/pcm_params.h>
30#include <sound/soc.h>
31
32#include <plat/dma.h>
33#include "omap-pcm.h"
34
35static const struct snd_pcm_hardware omap_pcm_hardware = {
36	.info			= SNDRV_PCM_INFO_MMAP |
37				  SNDRV_PCM_INFO_MMAP_VALID |
38				  SNDRV_PCM_INFO_INTERLEAVED |
39				  SNDRV_PCM_INFO_PAUSE |
40				  SNDRV_PCM_INFO_RESUME,
41	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
42				  SNDRV_PCM_FMTBIT_S32_LE,
43	.period_bytes_min	= 32,
44	.period_bytes_max	= 64 * 1024,
45	.periods_min		= 2,
46	.periods_max		= 255,
47	.buffer_bytes_max	= 128 * 1024,
48};
49
50struct omap_runtime_data {
51	spinlock_t			lock;
52	struct omap_pcm_dma_data	*dma_data;
53	int				dma_ch;
54	int				period_index;
55};
56
57static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
58{
59	struct snd_pcm_substream *substream = data;
60	struct snd_pcm_runtime *runtime = substream->runtime;
61	struct omap_runtime_data *prtd = runtime->private_data;
62	unsigned long flags;
63
64	if ((cpu_is_omap1510())) {
65		/*
66		 * OMAP1510 doesn't fully support DMA progress counter
67		 * and there is no software emulation implemented yet,
68		 * so have to maintain our own progress counters
69		 * that can be used by omap_pcm_pointer() instead.
70		 */
71		spin_lock_irqsave(&prtd->lock, flags);
72		if ((stat == OMAP_DMA_LAST_IRQ) &&
73				(prtd->period_index == runtime->periods - 1)) {
74			/* we are in sync, do nothing */
75			spin_unlock_irqrestore(&prtd->lock, flags);
76			return;
77		}
78		if (prtd->period_index >= 0) {
79			if (stat & OMAP_DMA_BLOCK_IRQ) {
80				/* end of buffer reached, loop back */
81				prtd->period_index = 0;
82			} else if (stat & OMAP_DMA_LAST_IRQ) {
83				/* update the counter for the last period */
84				prtd->period_index = runtime->periods - 1;
85			} else if (++prtd->period_index >= runtime->periods) {
86				/* end of buffer missed? loop back */
87				prtd->period_index = 0;
88			}
89		}
90		spin_unlock_irqrestore(&prtd->lock, flags);
91	}
92
93	snd_pcm_period_elapsed(substream);
94}
95
96/* this may get called several times by oss emulation */
97static int omap_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 snd_soc_pcm_runtime *rtd = substream->private_data;
102	struct omap_runtime_data *prtd = runtime->private_data;
103	struct omap_pcm_dma_data *dma_data;
104	int err = 0;
105
106	dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
107
108	if (!dma_data)
109		return 0;
110
111	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
112	runtime->dma_bytes = params_buffer_bytes(params);
113
114	if (prtd->dma_data)
115		return 0;
116	prtd->dma_data = dma_data;
117	err = omap_request_dma(dma_data->dma_req, dma_data->name,
118			       omap_pcm_dma_irq, substream, &prtd->dma_ch);
119	if (!err) {
120		/*
121		 * Link channel with itself so DMA doesn't need any
122		 * reprogramming while looping the buffer
123		 */
124		omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
125	}
126
127	return err;
128}
129
130static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
131{
132	struct snd_pcm_runtime *runtime = substream->runtime;
133	struct omap_runtime_data *prtd = runtime->private_data;
134
135	if (prtd->dma_data == NULL)
136		return 0;
137
138	omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
139	omap_free_dma(prtd->dma_ch);
140	prtd->dma_data = NULL;
141
142	snd_pcm_set_runtime_buffer(substream, NULL);
143
144	return 0;
145}
146
147static int omap_pcm_prepare(struct snd_pcm_substream *substream)
148{
149	struct snd_pcm_runtime *runtime = substream->runtime;
150	struct omap_runtime_data *prtd = runtime->private_data;
151	struct omap_pcm_dma_data *dma_data = prtd->dma_data;
152	struct omap_dma_channel_params dma_params;
153	int bytes;
154
155	if (!prtd->dma_data)
156		return 0;
157
158	memset(&dma_params, 0, sizeof(dma_params));
159	dma_params.data_type			= dma_data->data_type;
160	dma_params.trigger			= dma_data->dma_req;
161	dma_params.sync_mode			= dma_data->sync_mode;
162	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
163		dma_params.src_amode		= OMAP_DMA_AMODE_POST_INC;
164		dma_params.dst_amode		= OMAP_DMA_AMODE_CONSTANT;
165		dma_params.src_or_dst_synch	= OMAP_DMA_DST_SYNC;
166		dma_params.src_start		= runtime->dma_addr;
167		dma_params.dst_start		= dma_data->port_addr;
168		dma_params.dst_port		= OMAP_DMA_PORT_MPUI;
169		dma_params.dst_fi		= dma_data->packet_size;
170	} else {
171		dma_params.src_amode		= OMAP_DMA_AMODE_CONSTANT;
172		dma_params.dst_amode		= OMAP_DMA_AMODE_POST_INC;
173		dma_params.src_or_dst_synch	= OMAP_DMA_SRC_SYNC;
174		dma_params.src_start		= dma_data->port_addr;
175		dma_params.dst_start		= runtime->dma_addr;
176		dma_params.src_port		= OMAP_DMA_PORT_MPUI;
177		dma_params.src_fi		= dma_data->packet_size;
178	}
179	/*
180	 * Set DMA transfer frame size equal to ALSA period size and frame
181	 * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
182	 * we can transfer the whole ALSA buffer with single DMA transfer but
183	 * still can get an interrupt at each period bounary
184	 */
185	bytes = snd_pcm_lib_period_bytes(substream);
186	dma_params.elem_count	= bytes >> dma_data->data_type;
187	dma_params.frame_count	= runtime->periods;
188	omap_set_dma_params(prtd->dma_ch, &dma_params);
189
190	if ((cpu_is_omap1510()))
191		omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ |
192			      OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ);
193	else
194		omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);
195
196	if (!(cpu_class_is_omap1())) {
197		omap_set_dma_src_burst_mode(prtd->dma_ch,
198						OMAP_DMA_DATA_BURST_16);
199		omap_set_dma_dest_burst_mode(prtd->dma_ch,
200						OMAP_DMA_DATA_BURST_16);
201	}
202
203	return 0;
204}
205
206static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
207{
208	struct snd_pcm_runtime *runtime = substream->runtime;
209	struct omap_runtime_data *prtd = runtime->private_data;
210	struct omap_pcm_dma_data *dma_data = prtd->dma_data;
211	unsigned long flags;
212	int ret = 0;
213
214	spin_lock_irqsave(&prtd->lock, flags);
215	switch (cmd) {
216	case SNDRV_PCM_TRIGGER_START:
217	case SNDRV_PCM_TRIGGER_RESUME:
218	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
219		prtd->period_index = 0;
220		/* Configure McBSP internal buffer usage */
221		if (dma_data->set_threshold)
222			dma_data->set_threshold(substream);
223
224		omap_start_dma(prtd->dma_ch);
225		break;
226
227	case SNDRV_PCM_TRIGGER_STOP:
228	case SNDRV_PCM_TRIGGER_SUSPEND:
229	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
230		prtd->period_index = -1;
231		omap_stop_dma(prtd->dma_ch);
232		break;
233	default:
234		ret = -EINVAL;
235	}
236	spin_unlock_irqrestore(&prtd->lock, flags);
237
238	return ret;
239}
240
241static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
242{
243	struct snd_pcm_runtime *runtime = substream->runtime;
244	struct omap_runtime_data *prtd = runtime->private_data;
245	dma_addr_t ptr;
246	snd_pcm_uframes_t offset;
247
248	if (cpu_is_omap1510()) {
249		offset = prtd->period_index * runtime->period_size;
250	} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
251		ptr = omap_get_dma_dst_pos(prtd->dma_ch);
252		offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
253	} else {
254		ptr = omap_get_dma_src_pos(prtd->dma_ch);
255		offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
256	}
257
258	if (offset >= runtime->buffer_size)
259		offset = 0;
260
261	return offset;
262}
263
264static int omap_pcm_open(struct snd_pcm_substream *substream)
265{
266	struct snd_pcm_runtime *runtime = substream->runtime;
267	struct omap_runtime_data *prtd;
268	int ret;
269
270	snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
271
272	/* Ensure that buffer size is a multiple of period size */
273	ret = snd_pcm_hw_constraint_integer(runtime,
274					    SNDRV_PCM_HW_PARAM_PERIODS);
275	if (ret < 0)
276		goto out;
277
278	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
279	if (prtd == NULL) {
280		ret = -ENOMEM;
281		goto out;
282	}
283	spin_lock_init(&prtd->lock);
284	runtime->private_data = prtd;
285
286out:
287	return ret;
288}
289
290static int omap_pcm_close(struct snd_pcm_substream *substream)
291{
292	struct snd_pcm_runtime *runtime = substream->runtime;
293
294	kfree(runtime->private_data);
295	return 0;
296}
297
298static int omap_pcm_mmap(struct snd_pcm_substream *substream,
299	struct vm_area_struct *vma)
300{
301	struct snd_pcm_runtime *runtime = substream->runtime;
302
303	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
304				     runtime->dma_area,
305				     runtime->dma_addr,
306				     runtime->dma_bytes);
307}
308
309static struct snd_pcm_ops omap_pcm_ops = {
310	.open		= omap_pcm_open,
311	.close		= omap_pcm_close,
312	.ioctl		= snd_pcm_lib_ioctl,
313	.hw_params	= omap_pcm_hw_params,
314	.hw_free	= omap_pcm_hw_free,
315	.prepare	= omap_pcm_prepare,
316	.trigger	= omap_pcm_trigger,
317	.pointer	= omap_pcm_pointer,
318	.mmap		= omap_pcm_mmap,
319};
320
321static u64 omap_pcm_dmamask = DMA_BIT_MASK(64);
322
323static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
324	int stream)
325{
326	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
327	struct snd_dma_buffer *buf = &substream->dma_buffer;
328	size_t size = omap_pcm_hardware.buffer_bytes_max;
329
330	buf->dev.type = SNDRV_DMA_TYPE_DEV;
331	buf->dev.dev = pcm->card->dev;
332	buf->private_data = NULL;
333	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
334					   &buf->addr, GFP_KERNEL);
335	if (!buf->area)
336		return -ENOMEM;
337
338	buf->bytes = size;
339	return 0;
340}
341
342static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
343{
344	struct snd_pcm_substream *substream;
345	struct snd_dma_buffer *buf;
346	int stream;
347
348	for (stream = 0; stream < 2; stream++) {
349		substream = pcm->streams[stream].substream;
350		if (!substream)
351			continue;
352
353		buf = &substream->dma_buffer;
354		if (!buf->area)
355			continue;
356
357		dma_free_writecombine(pcm->card->dev, buf->bytes,
358				      buf->area, buf->addr);
359		buf->area = NULL;
360	}
361}
362
363static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
364		 struct snd_pcm *pcm)
365{
366	int ret = 0;
367
368	if (!card->dev->dma_mask)
369		card->dev->dma_mask = &omap_pcm_dmamask;
370	if (!card->dev->coherent_dma_mask)
371		card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
372
373	if (dai->playback.channels_min) {
374		ret = omap_pcm_preallocate_dma_buffer(pcm,
375			SNDRV_PCM_STREAM_PLAYBACK);
376		if (ret)
377			goto out;
378	}
379
380	if (dai->capture.channels_min) {
381		ret = omap_pcm_preallocate_dma_buffer(pcm,
382			SNDRV_PCM_STREAM_CAPTURE);
383		if (ret)
384			goto out;
385	}
386
387out:
388	return ret;
389}
390
391struct snd_soc_platform omap_soc_platform = {
392	.name		= "omap-pcm-audio",
393	.pcm_ops 	= &omap_pcm_ops,
394	.pcm_new	= omap_pcm_new,
395	.pcm_free	= omap_pcm_free_dma_buffers,
396};
397EXPORT_SYMBOL_GPL(omap_soc_platform);
398
399static int __init omap_soc_platform_init(void)
400{
401	return snd_soc_register_platform(&omap_soc_platform);
402}
403module_init(omap_soc_platform_init);
404
405static void __exit omap_soc_platform_exit(void)
406{
407	snd_soc_unregister_platform(&omap_soc_platform);
408}
409module_exit(omap_soc_platform_exit);
410
411MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
412MODULE_DESCRIPTION("OMAP PCM DMA module");
413MODULE_LICENSE("GPL");
414