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,snd_pcm_uframes_t pos,void * buf,snd_pcm_uframes_t count)228 static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
229 	snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
230 {
231 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
232 	struct snd_pcm_runtime *runtime = substream->runtime;
233 	unsigned int sample_size = runtime->sample_bits / 8;
234 	struct bf5xx_i2s_pcm_data *dma_data;
235 	unsigned int i;
236 	void *src, *dst;
237 
238 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
239 
240 	if (dma_data->tdm_mode) {
241 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
242 			src = buf;
243 			dst = runtime->dma_area;
244 			dst += pos * sample_size * 8;
245 
246 			while (count--) {
247 				for (i = 0; i < runtime->channels; i++) {
248 					memcpy(dst + dma_data->map[i] *
249 						sample_size, src, sample_size);
250 					src += sample_size;
251 				}
252 				dst += 8 * sample_size;
253 			}
254 		} else {
255 			src = runtime->dma_area;
256 			src += pos * sample_size * 8;
257 			dst = buf;
258 
259 			while (count--) {
260 				for (i = 0; i < runtime->channels; i++) {
261 					memcpy(dst, src + dma_data->map[i] *
262 						sample_size, sample_size);
263 					dst += sample_size;
264 				}
265 				src += 8 * sample_size;
266 			}
267 		}
268 	} else {
269 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
270 			src = buf;
271 			dst = runtime->dma_area;
272 			dst += frames_to_bytes(runtime, pos);
273 		} else {
274 			src = runtime->dma_area;
275 			src += frames_to_bytes(runtime, pos);
276 			dst = buf;
277 		}
278 
279 		memcpy(dst, src, frames_to_bytes(runtime, count));
280 	}
281 
282 	return 0;
283 }
284 
bf5xx_pcm_silence(struct snd_pcm_substream * substream,int channel,snd_pcm_uframes_t pos,snd_pcm_uframes_t count)285 static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
286 	int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
287 {
288 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
289 	struct snd_pcm_runtime *runtime = substream->runtime;
290 	unsigned int sample_size = runtime->sample_bits / 8;
291 	void *buf = runtime->dma_area;
292 	struct bf5xx_i2s_pcm_data *dma_data;
293 	unsigned int offset, samples;
294 
295 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
296 
297 	if (dma_data->tdm_mode) {
298 		offset = pos * 8 * sample_size;
299 		samples = count * 8;
300 	} else {
301 		offset = frames_to_bytes(runtime, pos);
302 		samples = count * runtime->channels;
303 	}
304 
305 	snd_pcm_format_set_silence(runtime->format, buf + offset, samples);
306 
307 	return 0;
308 }
309 
310 static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
311 	.open		= bf5xx_pcm_open,
312 	.ioctl		= snd_pcm_lib_ioctl,
313 	.hw_params	= bf5xx_pcm_hw_params,
314 	.hw_free	= bf5xx_pcm_hw_free,
315 	.prepare	= bf5xx_pcm_prepare,
316 	.trigger	= bf5xx_pcm_trigger,
317 	.pointer	= bf5xx_pcm_pointer,
318 	.mmap		= bf5xx_pcm_mmap,
319 	.copy		= bf5xx_pcm_copy,
320 	.silence	= bf5xx_pcm_silence,
321 };
322 
bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime * rtd)323 static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd)
324 {
325 	struct snd_card *card = rtd->card->snd_card;
326 	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
327 	int ret;
328 
329 	pr_debug("%s enter\n", __func__);
330 	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
331 	if (ret)
332 		return ret;
333 
334 	return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
335 				SNDRV_DMA_TYPE_DEV, card->dev, size, size);
336 }
337 
338 static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
339 	.ops		= &bf5xx_pcm_i2s_ops,
340 	.pcm_new	= bf5xx_pcm_i2s_new,
341 };
342 
bfin_i2s_soc_platform_probe(struct platform_device * pdev)343 static int bfin_i2s_soc_platform_probe(struct platform_device *pdev)
344 {
345 	return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
346 }
347 
bfin_i2s_soc_platform_remove(struct platform_device * pdev)348 static int bfin_i2s_soc_platform_remove(struct platform_device *pdev)
349 {
350 	snd_soc_unregister_platform(&pdev->dev);
351 	return 0;
352 }
353 
354 static struct platform_driver bfin_i2s_pcm_driver = {
355 	.driver = {
356 		.name = "bfin-i2s-pcm-audio",
357 	},
358 
359 	.probe = bfin_i2s_soc_platform_probe,
360 	.remove = bfin_i2s_soc_platform_remove,
361 };
362 
363 module_platform_driver(bfin_i2s_pcm_driver);
364 
365 MODULE_AUTHOR("Cliff Cai");
366 MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
367 MODULE_LICENSE("GPL");
368