• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * File:         sound/soc/blackfin/bf5xx-i2s-pcm.c
3  * Author:       Cliff Cai <Cliff.Cai@analog.com>
4  *
5  * Created:      Tue June 06 2008
6  * Description:  DMA driver for i2s codec
7  *
8  * Modified:
9  *               Copyright 2008 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-i2s-pcm.h"
43 #include "bf5xx-sport.h"
44 
bf5xx_dma_irq(void * data)45 static void bf5xx_dma_irq(void *data)
46 {
47 	struct snd_pcm_substream *pcm = data;
48 	snd_pcm_period_elapsed(pcm);
49 }
50 
51 static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
52 	.info			= SNDRV_PCM_INFO_INTERLEAVED |
53 				   SNDRV_PCM_INFO_MMAP |
54 				   SNDRV_PCM_INFO_MMAP_VALID |
55 				   SNDRV_PCM_INFO_BLOCK_TRANSFER,
56 	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
57 				   SNDRV_PCM_FMTBIT_S24_LE |
58 				   SNDRV_PCM_FMTBIT_S32_LE,
59 	.period_bytes_min	= 32,
60 	.period_bytes_max	= 0x10000,
61 	.periods_min		= 1,
62 	.periods_max		= PAGE_SIZE/32,
63 	.buffer_bytes_max	= 0x20000, /* 128 kbytes */
64 	.fifo_size		= 16,
65 };
66 
bf5xx_pcm_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)67 static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
68 	struct snd_pcm_hw_params *params)
69 {
70 	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
71 	snd_pcm_lib_malloc_pages(substream, size);
72 
73 	return 0;
74 }
75 
bf5xx_pcm_hw_free(struct snd_pcm_substream * substream)76 static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
77 {
78 	snd_pcm_lib_free_pages(substream);
79 
80 	return 0;
81 }
82 
bf5xx_pcm_prepare(struct snd_pcm_substream * substream)83 static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
84 {
85 	struct snd_pcm_runtime *runtime = substream->runtime;
86 	struct sport_device *sport = runtime->private_data;
87 	int period_bytes = frames_to_bytes(runtime, runtime->period_size);
88 
89 	pr_debug("%s enter\n", __func__);
90 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
91 		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
92 		sport_config_tx_dma(sport, runtime->dma_area,
93 			runtime->periods, period_bytes);
94 	} else {
95 		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
96 		sport_config_rx_dma(sport, runtime->dma_area,
97 			runtime->periods, period_bytes);
98 	}
99 
100 	return 0;
101 }
102 
bf5xx_pcm_trigger(struct snd_pcm_substream * substream,int cmd)103 static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
104 {
105 	struct snd_pcm_runtime *runtime = substream->runtime;
106 	struct sport_device *sport = runtime->private_data;
107 	int ret = 0;
108 
109 	pr_debug("%s enter\n", __func__);
110 	switch (cmd) {
111 	case SNDRV_PCM_TRIGGER_START:
112 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
113 			sport_tx_start(sport);
114 		else
115 			sport_rx_start(sport);
116 		break;
117 	case SNDRV_PCM_TRIGGER_STOP:
118 	case SNDRV_PCM_TRIGGER_SUSPEND:
119 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
120 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
121 			sport_tx_stop(sport);
122 		else
123 			sport_rx_stop(sport);
124 		break;
125 	default:
126 		ret = -EINVAL;
127 	}
128 
129 	return ret;
130 }
131 
bf5xx_pcm_pointer(struct snd_pcm_substream * substream)132 static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
133 {
134 	struct snd_pcm_runtime *runtime = substream->runtime;
135 	struct sport_device *sport = runtime->private_data;
136 	unsigned int diff;
137 	snd_pcm_uframes_t frames;
138 	pr_debug("%s enter\n", __func__);
139 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
140 		diff = sport_curr_offset_tx(sport);
141 	} else {
142 		diff = sport_curr_offset_rx(sport);
143 	}
144 
145 	/*
146 	 * TX at least can report one frame beyond the end of the
147 	 * buffer if we hit the wraparound case - clamp to within the
148 	 * buffer as the ALSA APIs require.
149 	 */
150 	if (diff == snd_pcm_lib_buffer_bytes(substream))
151 		diff = 0;
152 
153 	frames = bytes_to_frames(substream->runtime, diff);
154 
155 	return frames;
156 }
157 
bf5xx_pcm_open(struct snd_pcm_substream * substream)158 static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
159 {
160 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
161 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
162 	struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
163 	struct snd_pcm_runtime *runtime = substream->runtime;
164 	struct snd_dma_buffer *buf = &substream->dma_buffer;
165 	int ret;
166 
167 	pr_debug("%s enter\n", __func__);
168 
169 	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
170 
171 	ret = snd_pcm_hw_constraint_integer(runtime,
172 			SNDRV_PCM_HW_PARAM_PERIODS);
173 	if (ret < 0)
174 		goto out;
175 
176 	if (sport_handle != NULL) {
177 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
178 			sport_handle->tx_buf = buf->area;
179 		else
180 			sport_handle->rx_buf = buf->area;
181 
182 		runtime->private_data = sport_handle;
183 	} else {
184 		pr_err("sport_handle is NULL\n");
185 		return -1;
186 	}
187 	return 0;
188 
189  out:
190 	return ret;
191 }
192 
bf5xx_pcm_mmap(struct snd_pcm_substream * substream,struct vm_area_struct * vma)193 static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
194 	struct vm_area_struct *vma)
195 {
196 	struct snd_pcm_runtime *runtime = substream->runtime;
197 	size_t size = vma->vm_end - vma->vm_start;
198 	vma->vm_start = (unsigned long)runtime->dma_area;
199 	vma->vm_end = vma->vm_start + size;
200 	vma->vm_flags |=  VM_SHARED;
201 
202 	return 0 ;
203 }
204 
205 static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
206 	.open		= bf5xx_pcm_open,
207 	.ioctl		= snd_pcm_lib_ioctl,
208 	.hw_params	= bf5xx_pcm_hw_params,
209 	.hw_free	= bf5xx_pcm_hw_free,
210 	.prepare	= bf5xx_pcm_prepare,
211 	.trigger	= bf5xx_pcm_trigger,
212 	.pointer	= bf5xx_pcm_pointer,
213 	.mmap		= bf5xx_pcm_mmap,
214 };
215 
bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm * pcm,int stream)216 static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
217 {
218 	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
219 	struct snd_dma_buffer *buf = &substream->dma_buffer;
220 	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
221 
222 	buf->dev.type = SNDRV_DMA_TYPE_DEV;
223 	buf->dev.dev = pcm->card->dev;
224 	buf->private_data = NULL;
225 	buf->area = dma_alloc_coherent(pcm->card->dev, size,
226 			&buf->addr, GFP_KERNEL);
227 	if (!buf->area) {
228 		pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
229 		return -ENOMEM;
230 	}
231 	buf->bytes = size;
232 
233 	pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
234 		buf->area, buf->bytes);
235 
236 	return 0;
237 }
238 
bf5xx_pcm_free_dma_buffers(struct snd_pcm * pcm)239 static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
240 {
241 	struct snd_pcm_substream *substream;
242 	struct snd_dma_buffer *buf;
243 	int stream;
244 
245 	for (stream = 0; stream < 2; stream++) {
246 		substream = pcm->streams[stream].substream;
247 		if (!substream)
248 			continue;
249 
250 		buf = &substream->dma_buffer;
251 		if (!buf->area)
252 			continue;
253 		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
254 		buf->area = NULL;
255 	}
256 }
257 
258 static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
259 
bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime * rtd)260 static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
261 {
262 	struct snd_card *card = rtd->card->snd_card;
263 	struct snd_pcm *pcm = rtd->pcm;
264 	int ret = 0;
265 
266 	pr_debug("%s enter\n", __func__);
267 	if (!card->dev->dma_mask)
268 		card->dev->dma_mask = &bf5xx_pcm_dmamask;
269 	if (!card->dev->coherent_dma_mask)
270 		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
271 
272 	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
273 		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
274 			SNDRV_PCM_STREAM_PLAYBACK);
275 		if (ret)
276 			goto out;
277 	}
278 
279 	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
280 		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
281 			SNDRV_PCM_STREAM_CAPTURE);
282 		if (ret)
283 			goto out;
284 	}
285  out:
286 	return ret;
287 }
288 
289 static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
290 	.ops		= &bf5xx_pcm_i2s_ops,
291 	.pcm_new	= bf5xx_pcm_i2s_new,
292 	.pcm_free	= bf5xx_pcm_free_dma_buffers,
293 };
294 
bfin_i2s_soc_platform_probe(struct platform_device * pdev)295 static int bfin_i2s_soc_platform_probe(struct platform_device *pdev)
296 {
297 	return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
298 }
299 
bfin_i2s_soc_platform_remove(struct platform_device * pdev)300 static int bfin_i2s_soc_platform_remove(struct platform_device *pdev)
301 {
302 	snd_soc_unregister_platform(&pdev->dev);
303 	return 0;
304 }
305 
306 static struct platform_driver bfin_i2s_pcm_driver = {
307 	.driver = {
308 		.name = "bfin-i2s-pcm-audio",
309 		.owner = THIS_MODULE,
310 	},
311 
312 	.probe = bfin_i2s_soc_platform_probe,
313 	.remove = bfin_i2s_soc_platform_remove,
314 };
315 
316 module_platform_driver(bfin_i2s_pcm_driver);
317 
318 MODULE_AUTHOR("Cliff Cai");
319 MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
320 MODULE_LICENSE("GPL");
321