• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
bf5xx_dma_irq(void * data)51 static void bf5xx_dma_irq(void *data)
52 {
53 	struct snd_pcm_substream *pcm = data;
54 	snd_pcm_period_elapsed(pcm);
55 }
56 
57 static 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 
bf5xx_pcm_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)71 static 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 
bf5xx_pcm_hw_free(struct snd_pcm_substream * substream)80 static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
81 {
82 	snd_pcm_lib_free_pages(substream);
83 
84 	return 0;
85 }
86 
bf5xx_pcm_prepare(struct snd_pcm_substream * substream)87 static 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 
bf5xx_pcm_trigger(struct snd_pcm_substream * substream,int cmd)110 static 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 
bf5xx_pcm_pointer(struct snd_pcm_substream * substream)138 static 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 
bf5xx_pcm_open(struct snd_pcm_substream * substream)155 static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
156 {
157 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
158 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
159 	struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
160 	struct snd_pcm_runtime *runtime = substream->runtime;
161 	struct snd_dma_buffer *buf = &substream->dma_buffer;
162 
163 	int ret = 0;
164 
165 	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
166 
167 	ret = snd_pcm_hw_constraint_integer(runtime,
168 		SNDRV_PCM_HW_PARAM_PERIODS);
169 	if (ret < 0)
170 		goto out;
171 
172 	if (sport_handle != NULL) {
173 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
174 			sport_handle->tx_buf = buf->area;
175 		else
176 			sport_handle->rx_buf = buf->area;
177 
178 		runtime->private_data = sport_handle;
179 	} else {
180 		pr_err("sport_handle is NULL\n");
181 		ret = -ENODEV;
182 	}
183 out:
184 	return ret;
185 }
186 
bf5xx_pcm_copy(struct snd_pcm_substream * substream,int channel,snd_pcm_uframes_t pos,void * buf,snd_pcm_uframes_t count)187 static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
188 	snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
189 {
190 	struct snd_pcm_runtime *runtime = substream->runtime;
191 	struct sport_device *sport = runtime->private_data;
192 	struct bf5xx_tdm_port *tdm_port = sport->private_data;
193 	unsigned int *src;
194 	unsigned int *dst;
195 	int i;
196 
197 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
198 		src = buf;
199 		dst = (unsigned int *)substream->runtime->dma_area;
200 
201 		dst += pos * 8;
202 		while (count--) {
203 			for (i = 0; i < substream->runtime->channels; i++)
204 				*(dst + tdm_port->tx_map[i]) = *src++;
205 			dst += 8;
206 		}
207 	} else {
208 		src = (unsigned int *)substream->runtime->dma_area;
209 		dst = buf;
210 
211 		src += pos * 8;
212 		while (count--) {
213 			for (i = 0; i < substream->runtime->channels; i++)
214 				*dst++ = *(src + tdm_port->rx_map[i]);
215 			src += 8;
216 		}
217 	}
218 
219 	return 0;
220 }
221 
bf5xx_pcm_silence(struct snd_pcm_substream * substream,int channel,snd_pcm_uframes_t pos,snd_pcm_uframes_t count)222 static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
223 	int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
224 {
225 	unsigned char *buf = substream->runtime->dma_area;
226 	buf += pos * 8 * 4;
227 	memset(buf, '\0', count * 8 * 4);
228 
229 	return 0;
230 }
231 
232 
233 struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
234 	.open           = bf5xx_pcm_open,
235 	.ioctl          = snd_pcm_lib_ioctl,
236 	.hw_params      = bf5xx_pcm_hw_params,
237 	.hw_free        = bf5xx_pcm_hw_free,
238 	.prepare        = bf5xx_pcm_prepare,
239 	.trigger        = bf5xx_pcm_trigger,
240 	.pointer        = bf5xx_pcm_pointer,
241 	.copy           = bf5xx_pcm_copy,
242 	.silence        = bf5xx_pcm_silence,
243 };
244 
bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm * pcm,int stream)245 static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
246 {
247 	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
248 	struct snd_dma_buffer *buf = &substream->dma_buffer;
249 	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
250 
251 	buf->dev.type = SNDRV_DMA_TYPE_DEV;
252 	buf->dev.dev = pcm->card->dev;
253 	buf->private_data = NULL;
254 	buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
255 		&buf->addr, GFP_KERNEL);
256 	if (!buf->area) {
257 		pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
258 		return -ENOMEM;
259 	}
260 	buf->bytes = size;
261 
262 	return 0;
263 }
264 
bf5xx_pcm_free_dma_buffers(struct snd_pcm * pcm)265 static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
266 {
267 	struct snd_pcm_substream *substream;
268 	struct snd_dma_buffer *buf;
269 	int stream;
270 
271 	for (stream = 0; stream < 2; stream++) {
272 		substream = pcm->streams[stream].substream;
273 		if (!substream)
274 			continue;
275 
276 		buf = &substream->dma_buffer;
277 		if (!buf->area)
278 			continue;
279 		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
280 		buf->area = NULL;
281 	}
282 }
283 
284 static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
285 
bf5xx_pcm_tdm_new(struct snd_soc_pcm_runtime * rtd)286 static int bf5xx_pcm_tdm_new(struct snd_soc_pcm_runtime *rtd)
287 {
288 	struct snd_card *card = rtd->card->snd_card;
289 	struct snd_pcm *pcm = rtd->pcm;
290 	int ret = 0;
291 
292 	if (!card->dev->dma_mask)
293 		card->dev->dma_mask = &bf5xx_pcm_dmamask;
294 	if (!card->dev->coherent_dma_mask)
295 		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
296 
297 	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
298 		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
299 			SNDRV_PCM_STREAM_PLAYBACK);
300 		if (ret)
301 			goto out;
302 	}
303 
304 	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
305 		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
306 			SNDRV_PCM_STREAM_CAPTURE);
307 		if (ret)
308 			goto out;
309 	}
310 out:
311 	return ret;
312 }
313 
314 static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = {
315 	.ops        = &bf5xx_pcm_tdm_ops,
316 	.pcm_new        = bf5xx_pcm_tdm_new,
317 	.pcm_free       = bf5xx_pcm_free_dma_buffers,
318 };
319 
bf5xx_soc_platform_probe(struct platform_device * pdev)320 static int bf5xx_soc_platform_probe(struct platform_device *pdev)
321 {
322 	return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
323 }
324 
bf5xx_soc_platform_remove(struct platform_device * pdev)325 static int bf5xx_soc_platform_remove(struct platform_device *pdev)
326 {
327 	snd_soc_unregister_platform(&pdev->dev);
328 	return 0;
329 }
330 
331 static struct platform_driver bfin_tdm_driver = {
332 	.driver = {
333 			.name = "bfin-tdm-pcm-audio",
334 			.owner = THIS_MODULE,
335 	},
336 
337 	.probe = bf5xx_soc_platform_probe,
338 	.remove = bf5xx_soc_platform_remove,
339 };
340 
341 module_platform_driver(bfin_tdm_driver);
342 
343 MODULE_AUTHOR("Barry Song");
344 MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
345 MODULE_LICENSE("GPL");
346