• 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-sport.h"
43 #include "bf5xx-i2s-pcm.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_VALID |
54 				   SNDRV_PCM_INFO_BLOCK_TRANSFER,
55 	.period_bytes_min	= 32,
56 	.period_bytes_max	= 0x10000,
57 	.periods_min		= 1,
58 	.periods_max		= PAGE_SIZE/32,
59 	.buffer_bytes_max	= 0x20000, /* 128 kbytes */
60 	.fifo_size		= 16,
61 };
62 
bf5xx_pcm_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)63 static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
64 	struct snd_pcm_hw_params *params)
65 {
66 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
67 	unsigned int buffer_size = params_buffer_bytes(params);
68 	struct bf5xx_i2s_pcm_data *dma_data;
69 
70 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
71 
72 	if (dma_data->tdm_mode)
73 		buffer_size = buffer_size / params_channels(params) * 8;
74 
75 	return snd_pcm_lib_malloc_pages(substream, buffer_size);
76 }
77 
bf5xx_pcm_hw_free(struct snd_pcm_substream * substream)78 static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
79 {
80 	snd_pcm_lib_free_pages(substream);
81 
82 	return 0;
83 }
84 
bf5xx_pcm_prepare(struct snd_pcm_substream * substream)85 static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
86 {
87 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
88 	struct snd_pcm_runtime *runtime = substream->runtime;
89 	struct sport_device *sport = runtime->private_data;
90 	int period_bytes = frames_to_bytes(runtime, runtime->period_size);
91 	struct bf5xx_i2s_pcm_data *dma_data;
92 
93 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
94 
95 	if (dma_data->tdm_mode)
96 		period_bytes = period_bytes / runtime->channels * 8;
97 
98 	pr_debug("%s enter\n", __func__);
99 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
100 		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
101 		sport_config_tx_dma(sport, runtime->dma_area,
102 			runtime->periods, period_bytes);
103 	} else {
104 		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
105 		sport_config_rx_dma(sport, runtime->dma_area,
106 			runtime->periods, period_bytes);
107 	}
108 
109 	return 0;
110 }
111 
bf5xx_pcm_trigger(struct snd_pcm_substream * substream,int cmd)112 static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
113 {
114 	struct snd_pcm_runtime *runtime = substream->runtime;
115 	struct sport_device *sport = runtime->private_data;
116 	int ret = 0;
117 
118 	pr_debug("%s enter\n", __func__);
119 	switch (cmd) {
120 	case SNDRV_PCM_TRIGGER_START:
121 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
122 			sport_tx_start(sport);
123 		else
124 			sport_rx_start(sport);
125 		break;
126 	case SNDRV_PCM_TRIGGER_STOP:
127 	case SNDRV_PCM_TRIGGER_SUSPEND:
128 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
129 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
130 			sport_tx_stop(sport);
131 		else
132 			sport_rx_stop(sport);
133 		break;
134 	default:
135 		ret = -EINVAL;
136 	}
137 
138 	return ret;
139 }
140 
bf5xx_pcm_pointer(struct snd_pcm_substream * substream)141 static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
142 {
143 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
144 	struct snd_pcm_runtime *runtime = substream->runtime;
145 	struct sport_device *sport = runtime->private_data;
146 	unsigned int diff;
147 	snd_pcm_uframes_t frames;
148 	struct bf5xx_i2s_pcm_data *dma_data;
149 
150 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
151 
152 	pr_debug("%s enter\n", __func__);
153 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
154 		diff = sport_curr_offset_tx(sport);
155 	} else {
156 		diff = sport_curr_offset_rx(sport);
157 	}
158 
159 	/*
160 	 * TX at least can report one frame beyond the end of the
161 	 * buffer if we hit the wraparound case - clamp to within the
162 	 * buffer as the ALSA APIs require.
163 	 */
164 	if (diff == snd_pcm_lib_buffer_bytes(substream))
165 		diff = 0;
166 
167 	frames = bytes_to_frames(substream->runtime, diff);
168 	if (dma_data->tdm_mode)
169 		frames = frames * runtime->channels / 8;
170 
171 	return frames;
172 }
173 
bf5xx_pcm_open(struct snd_pcm_substream * substream)174 static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
175 {
176 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
177 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
178 	struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
179 	struct snd_pcm_runtime *runtime = substream->runtime;
180 	struct snd_dma_buffer *buf = &substream->dma_buffer;
181 	struct bf5xx_i2s_pcm_data *dma_data;
182 	int ret;
183 
184 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
185 
186 	pr_debug("%s enter\n", __func__);
187 
188 	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
189 	if (dma_data->tdm_mode)
190 		runtime->hw.buffer_bytes_max /= 4;
191 	else
192 		runtime->hw.info |= SNDRV_PCM_INFO_MMAP;
193 
194 	ret = snd_pcm_hw_constraint_integer(runtime,
195 			SNDRV_PCM_HW_PARAM_PERIODS);
196 	if (ret < 0)
197 		goto out;
198 
199 	if (sport_handle != NULL) {
200 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
201 			sport_handle->tx_buf = buf->area;
202 		else
203 			sport_handle->rx_buf = buf->area;
204 
205 		runtime->private_data = sport_handle;
206 	} else {
207 		pr_err("sport_handle is NULL\n");
208 		return -1;
209 	}
210 	return 0;
211 
212  out:
213 	return ret;
214 }
215 
bf5xx_pcm_mmap(struct snd_pcm_substream * substream,struct vm_area_struct * vma)216 static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
217 	struct vm_area_struct *vma)
218 {
219 	struct snd_pcm_runtime *runtime = substream->runtime;
220 	size_t size = vma->vm_end - vma->vm_start;
221 	vma->vm_start = (unsigned long)runtime->dma_area;
222 	vma->vm_end = vma->vm_start + size;
223 	vma->vm_flags |=  VM_SHARED;
224 
225 	return 0 ;
226 }
227 
bf5xx_pcm_copy(struct snd_pcm_substream * substream,int channel,unsigned long pos,void * buf,unsigned long count)228 static int bf5xx_pcm_copy(struct snd_pcm_substream *substream,
229 			  int channel, unsigned long pos,
230 			  void *buf, unsigned long count)
231 {
232 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
233 	struct snd_pcm_runtime *runtime = substream->runtime;
234 	unsigned int sample_size = runtime->sample_bits / 8;
235 	struct bf5xx_i2s_pcm_data *dma_data;
236 	unsigned int i;
237 	void *src, *dst;
238 
239 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
240 
241 	if (dma_data->tdm_mode) {
242 		pos = bytes_to_frames(runtime, pos);
243 		count = bytes_to_frames(runtime, count);
244 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
245 			src = buf;
246 			dst = runtime->dma_area;
247 			dst += pos * sample_size * 8;
248 
249 			while (count--) {
250 				for (i = 0; i < runtime->channels; i++) {
251 					memcpy(dst + dma_data->map[i] *
252 						sample_size, src, sample_size);
253 					src += sample_size;
254 				}
255 				dst += 8 * sample_size;
256 			}
257 		} else {
258 			src = runtime->dma_area;
259 			src += pos * sample_size * 8;
260 			dst = buf;
261 
262 			while (count--) {
263 				for (i = 0; i < runtime->channels; i++) {
264 					memcpy(dst, src + dma_data->map[i] *
265 						sample_size, sample_size);
266 					dst += sample_size;
267 				}
268 				src += 8 * sample_size;
269 			}
270 		}
271 	} else {
272 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
273 			src = buf;
274 			dst = runtime->dma_area;
275 			dst += pos;
276 		} else {
277 			src = runtime->dma_area;
278 			src += pos;
279 			dst = buf;
280 		}
281 
282 		memcpy(dst, src, count);
283 	}
284 
285 	return 0;
286 }
287 
bf5xx_pcm_copy_user(struct snd_pcm_substream * substream,int channel,unsigned long pos,void __user * buf,unsigned long count)288 static int bf5xx_pcm_copy_user(struct snd_pcm_substream *substream,
289 			       int channel, unsigned long pos,
290 			       void __user *buf, unsigned long count)
291 {
292 	return bf5xx_pcm_copy(substream, channel, pos, (void *)buf, count);
293 }
294 
bf5xx_pcm_silence(struct snd_pcm_substream * substream,int channel,unsigned long pos,unsigned long count)295 static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
296 			     int channel, unsigned long pos,
297 			     unsigned long count)
298 {
299 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
300 	struct snd_pcm_runtime *runtime = substream->runtime;
301 	unsigned int sample_size = runtime->sample_bits / 8;
302 	void *buf = runtime->dma_area;
303 	struct bf5xx_i2s_pcm_data *dma_data;
304 	unsigned int offset, samples;
305 
306 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
307 
308 	if (dma_data->tdm_mode) {
309 		offset = bytes_to_frames(runtime, pos) * 8 * sample_size;
310 		samples = bytes_to_frames(runtime, count) * 8;
311 	} else {
312 		offset = pos;
313 		samples = bytes_to_samples(runtime, count);
314 	}
315 
316 	snd_pcm_format_set_silence(runtime->format, buf + offset, samples);
317 
318 	return 0;
319 }
320 
321 static const struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
322 	.open		= bf5xx_pcm_open,
323 	.ioctl		= snd_pcm_lib_ioctl,
324 	.hw_params	= bf5xx_pcm_hw_params,
325 	.hw_free	= bf5xx_pcm_hw_free,
326 	.prepare	= bf5xx_pcm_prepare,
327 	.trigger	= bf5xx_pcm_trigger,
328 	.pointer	= bf5xx_pcm_pointer,
329 	.mmap		= bf5xx_pcm_mmap,
330 	.copy_user	= bf5xx_pcm_copy_user,
331 	.copy_kernel	= bf5xx_pcm_copy,
332 	.fill_silence	= bf5xx_pcm_silence,
333 };
334 
bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime * rtd)335 static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
336 {
337 	struct snd_card *card = rtd->card->snd_card;
338 	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
339 	int ret;
340 
341 	pr_debug("%s enter\n", __func__);
342 	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
343 	if (ret)
344 		return ret;
345 
346 	return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
347 				SNDRV_DMA_TYPE_DEV, card->dev, size, size);
348 }
349 
350 static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
351 	.ops		= &bf5xx_pcm_i2s_ops,
352 	.pcm_new	= bf5xx_pcm_i2s_new,
353 };
354 
bfin_i2s_soc_platform_probe(struct platform_device * pdev)355 static int bfin_i2s_soc_platform_probe(struct platform_device *pdev)
356 {
357 	return devm_snd_soc_register_platform(&pdev->dev,
358 					      &bf5xx_i2s_soc_platform);
359 }
360 
361 static struct platform_driver bfin_i2s_pcm_driver = {
362 	.driver = {
363 		.name = "bfin-i2s-pcm-audio",
364 	},
365 
366 	.probe = bfin_i2s_soc_platform_probe,
367 };
368 
369 module_platform_driver(bfin_i2s_pcm_driver);
370 
371 MODULE_AUTHOR("Cliff Cai");
372 MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
373 MODULE_LICENSE("GPL");
374