• 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/blackfin/
1/*
2 * File:         sound/soc/blackfin/bf5xx-tdm-pcm.c
3 * Author:       Barry Song <Barry.Song@analog.com>
4 *
5 * Created:      Tue June 06 2009
6 * Description:  DMA driver for tdm codec
7 *
8 * Modified:
9 *               Copyright 2009 Analog Devices Inc.
10 *
11 * Bugs:         Enter bugs at http://blackfin.uclinux.org/
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see the file COPYING, or write
25 * to the Free Software Foundation, Inc.,
26 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
27 */
28
29#include <linux/module.h>
30#include <linux/init.h>
31#include <linux/platform_device.h>
32#include <linux/dma-mapping.h>
33#include <linux/gfp.h>
34
35#include <sound/core.h>
36#include <sound/pcm.h>
37#include <sound/pcm_params.h>
38#include <sound/soc.h>
39
40#include <asm/dma.h>
41
42#include "bf5xx-tdm-pcm.h"
43#include "bf5xx-tdm.h"
44#include "bf5xx-sport.h"
45
46#define PCM_BUFFER_MAX  0x8000
47#define FRAGMENT_SIZE_MIN  (4*1024)
48#define FRAGMENTS_MIN  2
49#define FRAGMENTS_MAX  32
50
51static void bf5xx_dma_irq(void *data)
52{
53	struct snd_pcm_substream *pcm = data;
54	snd_pcm_period_elapsed(pcm);
55}
56
57static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
58	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
59		SNDRV_PCM_INFO_RESUME),
60	.formats =          SNDRV_PCM_FMTBIT_S32_LE,
61	.rates =            SNDRV_PCM_RATE_48000,
62	.channels_min =     2,
63	.channels_max =     8,
64	.buffer_bytes_max = PCM_BUFFER_MAX,
65	.period_bytes_min = FRAGMENT_SIZE_MIN,
66	.period_bytes_max = PCM_BUFFER_MAX/2,
67	.periods_min =      FRAGMENTS_MIN,
68	.periods_max =      FRAGMENTS_MAX,
69};
70
71static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
72	struct snd_pcm_hw_params *params)
73{
74	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
75	snd_pcm_lib_malloc_pages(substream, size * 4);
76
77	return 0;
78}
79
80static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
81{
82	snd_pcm_lib_free_pages(substream);
83
84	return 0;
85}
86
87static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
88{
89	struct snd_pcm_runtime *runtime = substream->runtime;
90	struct sport_device *sport = runtime->private_data;
91	int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
92
93	fragsize_bytes /= runtime->channels;
94	/* inflate the fragsize to match the dma width of SPORT */
95	fragsize_bytes *= 8;
96
97	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
98		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
99		sport_config_tx_dma(sport, runtime->dma_area,
100			runtime->periods, fragsize_bytes);
101	} else {
102		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
103		sport_config_rx_dma(sport, runtime->dma_area,
104			runtime->periods, fragsize_bytes);
105	}
106
107	return 0;
108}
109
110static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
111{
112	struct snd_pcm_runtime *runtime = substream->runtime;
113	struct sport_device *sport = runtime->private_data;
114	int ret = 0;
115
116	switch (cmd) {
117	case SNDRV_PCM_TRIGGER_START:
118		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
119			sport_tx_start(sport);
120		else
121			sport_rx_start(sport);
122		break;
123	case SNDRV_PCM_TRIGGER_STOP:
124	case SNDRV_PCM_TRIGGER_SUSPEND:
125	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
126		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
127			sport_tx_stop(sport);
128		else
129			sport_rx_stop(sport);
130		break;
131	default:
132		ret = -EINVAL;
133	}
134
135	return ret;
136}
137
138static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
139{
140	struct snd_pcm_runtime *runtime = substream->runtime;
141	struct sport_device *sport = runtime->private_data;
142	unsigned int diff;
143	snd_pcm_uframes_t frames;
144
145	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
146		diff = sport_curr_offset_tx(sport);
147		frames = diff / (8*4); /* 32 bytes per frame */
148	} else {
149		diff = sport_curr_offset_rx(sport);
150		frames = diff / (8*4);
151	}
152	return frames;
153}
154
155static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
156{
157	struct snd_pcm_runtime *runtime = substream->runtime;
158	int ret = 0;
159
160	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
161
162	ret = snd_pcm_hw_constraint_integer(runtime,
163		SNDRV_PCM_HW_PARAM_PERIODS);
164	if (ret < 0)
165		goto out;
166
167	if (sport_handle != NULL)
168		runtime->private_data = sport_handle;
169	else {
170		pr_err("sport_handle is NULL\n");
171		ret = -ENODEV;
172	}
173out:
174	return ret;
175}
176
177static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
178	snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
179{
180	struct snd_pcm_runtime *runtime = substream->runtime;
181	struct sport_device *sport = runtime->private_data;
182	struct bf5xx_tdm_port *tdm_port = sport->private_data;
183	unsigned int *src;
184	unsigned int *dst;
185	int i;
186
187	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
188		src = buf;
189		dst = (unsigned int *)substream->runtime->dma_area;
190
191		dst += pos * 8;
192		while (count--) {
193			for (i = 0; i < substream->runtime->channels; i++)
194				*(dst + tdm_port->tx_map[i]) = *src++;
195			dst += 8;
196		}
197	} else {
198		src = (unsigned int *)substream->runtime->dma_area;
199		dst = buf;
200
201		src += pos * 8;
202		while (count--) {
203			for (i = 0; i < substream->runtime->channels; i++)
204				*dst++ = *(src + tdm_port->rx_map[i]);
205			src += 8;
206		}
207	}
208
209	return 0;
210}
211
212static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
213	int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
214{
215	unsigned char *buf = substream->runtime->dma_area;
216	buf += pos * 8 * 4;
217	memset(buf, '\0', count * 8 * 4);
218
219	return 0;
220}
221
222
223struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
224	.open           = bf5xx_pcm_open,
225	.ioctl          = snd_pcm_lib_ioctl,
226	.hw_params      = bf5xx_pcm_hw_params,
227	.hw_free        = bf5xx_pcm_hw_free,
228	.prepare        = bf5xx_pcm_prepare,
229	.trigger        = bf5xx_pcm_trigger,
230	.pointer        = bf5xx_pcm_pointer,
231	.copy           = bf5xx_pcm_copy,
232	.silence        = bf5xx_pcm_silence,
233};
234
235static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
236{
237	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
238	struct snd_dma_buffer *buf = &substream->dma_buffer;
239	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
240
241	buf->dev.type = SNDRV_DMA_TYPE_DEV;
242	buf->dev.dev = pcm->card->dev;
243	buf->private_data = NULL;
244	buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
245		&buf->addr, GFP_KERNEL);
246	if (!buf->area) {
247		pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
248		return -ENOMEM;
249	}
250	buf->bytes = size;
251
252	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
253		sport_handle->tx_buf = buf->area;
254	else
255		sport_handle->rx_buf = buf->area;
256
257	return 0;
258}
259
260static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
261{
262	struct snd_pcm_substream *substream;
263	struct snd_dma_buffer *buf;
264	int stream;
265
266	for (stream = 0; stream < 2; stream++) {
267		substream = pcm->streams[stream].substream;
268		if (!substream)
269			continue;
270
271		buf = &substream->dma_buffer;
272		if (!buf->area)
273			continue;
274		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
275		buf->area = NULL;
276	}
277	if (sport_handle)
278		sport_done(sport_handle);
279}
280
281static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
282
283static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
284	struct snd_pcm *pcm)
285{
286	int ret = 0;
287
288	if (!card->dev->dma_mask)
289		card->dev->dma_mask = &bf5xx_pcm_dmamask;
290	if (!card->dev->coherent_dma_mask)
291		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
292
293	if (dai->playback.channels_min) {
294		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
295			SNDRV_PCM_STREAM_PLAYBACK);
296		if (ret)
297			goto out;
298	}
299
300	if (dai->capture.channels_min) {
301		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
302			SNDRV_PCM_STREAM_CAPTURE);
303		if (ret)
304			goto out;
305	}
306out:
307	return ret;
308}
309
310struct snd_soc_platform bf5xx_tdm_soc_platform = {
311	.name           = "bf5xx-audio",
312	.pcm_ops        = &bf5xx_pcm_tdm_ops,
313	.pcm_new        = bf5xx_pcm_tdm_new,
314	.pcm_free       = bf5xx_pcm_free_dma_buffers,
315};
316EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform);
317
318static int __init bfin_pcm_tdm_init(void)
319{
320	return snd_soc_register_platform(&bf5xx_tdm_soc_platform);
321}
322module_init(bfin_pcm_tdm_init);
323
324static void __exit bfin_pcm_tdm_exit(void)
325{
326	snd_soc_unregister_platform(&bf5xx_tdm_soc_platform);
327}
328module_exit(bfin_pcm_tdm_exit);
329
330MODULE_AUTHOR("Barry Song");
331MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
332MODULE_LICENSE("GPL");
333