blob: a6b5457036ef099bac3aa1887e420d90cbe01879 [file] [log] [blame]
Barry Song01e2ab22009-07-27 18:06:39 +08001/*
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>
Barry Song01e2ab22009-07-27 18:06:39 +080032#include <linux/dma-mapping.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090033#include <linux/gfp.h>
Barry Song01e2ab22009-07-27 18:06:39 +080034
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
Barry Song01e2ab22009-07-27 18:06:39 +080042#include "bf5xx-tdm.h"
43#include "bf5xx-sport.h"
44
Barry Song08db48f2009-09-15 11:24:52 +080045#define PCM_BUFFER_MAX 0x8000
Barry Song01e2ab22009-07-27 18:06:39 +080046#define FRAGMENT_SIZE_MIN (4*1024)
47#define FRAGMENTS_MIN 2
48#define FRAGMENTS_MAX 32
49
50static void bf5xx_dma_irq(void *data)
51{
52 struct snd_pcm_substream *pcm = data;
53 snd_pcm_period_elapsed(pcm);
54}
55
56static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
57 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
58 SNDRV_PCM_INFO_RESUME),
59 .formats = SNDRV_PCM_FMTBIT_S32_LE,
60 .rates = SNDRV_PCM_RATE_48000,
61 .channels_min = 2,
62 .channels_max = 8,
63 .buffer_bytes_max = PCM_BUFFER_MAX,
64 .period_bytes_min = FRAGMENT_SIZE_MIN,
65 .period_bytes_max = PCM_BUFFER_MAX/2,
66 .periods_min = FRAGMENTS_MIN,
67 .periods_max = FRAGMENTS_MAX,
68};
69
70static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
71 struct snd_pcm_hw_params *params)
72{
73 size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
74 snd_pcm_lib_malloc_pages(substream, size * 4);
75
76 return 0;
77}
78
79static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
80{
81 snd_pcm_lib_free_pages(substream);
82
83 return 0;
84}
85
86static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
87{
88 struct snd_pcm_runtime *runtime = substream->runtime;
89 struct sport_device *sport = runtime->private_data;
90 int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
91
92 fragsize_bytes /= runtime->channels;
93 /* inflate the fragsize to match the dma width of SPORT */
94 fragsize_bytes *= 8;
95
96 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
97 sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
98 sport_config_tx_dma(sport, runtime->dma_area,
99 runtime->periods, fragsize_bytes);
100 } else {
101 sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
102 sport_config_rx_dma(sport, runtime->dma_area,
103 runtime->periods, fragsize_bytes);
104 }
105
106 return 0;
107}
108
109static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
110{
111 struct snd_pcm_runtime *runtime = substream->runtime;
112 struct sport_device *sport = runtime->private_data;
113 int ret = 0;
114
115 switch (cmd) {
116 case SNDRV_PCM_TRIGGER_START:
117 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
118 sport_tx_start(sport);
119 else
120 sport_rx_start(sport);
121 break;
122 case SNDRV_PCM_TRIGGER_STOP:
123 case SNDRV_PCM_TRIGGER_SUSPEND:
124 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
125 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
126 sport_tx_stop(sport);
127 else
128 sport_rx_stop(sport);
129 break;
130 default:
131 ret = -EINVAL;
132 }
133
134 return ret;
135}
136
137static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
138{
139 struct snd_pcm_runtime *runtime = substream->runtime;
140 struct sport_device *sport = runtime->private_data;
141 unsigned int diff;
142 snd_pcm_uframes_t frames;
143
144 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
145 diff = sport_curr_offset_tx(sport);
146 frames = diff / (8*4); /* 32 bytes per frame */
147 } else {
148 diff = sport_curr_offset_rx(sport);
149 frames = diff / (8*4);
150 }
151 return frames;
152}
153
154static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
155{
Barry Song2c66cb92011-03-28 01:45:10 -0400156 struct snd_soc_pcm_runtime *rtd = substream->private_data;
157 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
158 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
Barry Song01e2ab22009-07-27 18:06:39 +0800159 struct snd_pcm_runtime *runtime = substream->runtime;
Barry Song2c66cb92011-03-28 01:45:10 -0400160 struct snd_dma_buffer *buf = &substream->dma_buffer;
161
Barry Song01e2ab22009-07-27 18:06:39 +0800162 int ret = 0;
163
164 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
165
166 ret = snd_pcm_hw_constraint_integer(runtime,
167 SNDRV_PCM_HW_PARAM_PERIODS);
168 if (ret < 0)
169 goto out;
170
Barry Song2c66cb92011-03-28 01:45:10 -0400171 if (sport_handle != NULL) {
172 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
173 sport_handle->tx_buf = buf->area;
174 else
175 sport_handle->rx_buf = buf->area;
176
Barry Song01e2ab22009-07-27 18:06:39 +0800177 runtime->private_data = sport_handle;
Barry Song2c66cb92011-03-28 01:45:10 -0400178 } else {
Barry Song01e2ab22009-07-27 18:06:39 +0800179 pr_err("sport_handle is NULL\n");
180 ret = -ENODEV;
181 }
182out:
183 return ret;
184}
185
186static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
187 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
188{
Barry Song08db48f2009-09-15 11:24:52 +0800189 struct snd_pcm_runtime *runtime = substream->runtime;
190 struct sport_device *sport = runtime->private_data;
191 struct bf5xx_tdm_port *tdm_port = sport->private_data;
Barry Song01e2ab22009-07-27 18:06:39 +0800192 unsigned int *src;
193 unsigned int *dst;
194 int i;
195
196 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
197 src = buf;
198 dst = (unsigned int *)substream->runtime->dma_area;
199
200 dst += pos * 8;
201 while (count--) {
202 for (i = 0; i < substream->runtime->channels; i++)
Barry Song08db48f2009-09-15 11:24:52 +0800203 *(dst + tdm_port->tx_map[i]) = *src++;
Barry Song01e2ab22009-07-27 18:06:39 +0800204 dst += 8;
205 }
206 } else {
207 src = (unsigned int *)substream->runtime->dma_area;
208 dst = buf;
209
210 src += pos * 8;
211 while (count--) {
212 for (i = 0; i < substream->runtime->channels; i++)
Barry Song08db48f2009-09-15 11:24:52 +0800213 *dst++ = *(src + tdm_port->rx_map[i]);
Barry Song01e2ab22009-07-27 18:06:39 +0800214 src += 8;
215 }
216 }
217
218 return 0;
219}
220
221static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
222 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
223{
224 unsigned char *buf = substream->runtime->dma_area;
225 buf += pos * 8 * 4;
226 memset(buf, '\0', count * 8 * 4);
227
228 return 0;
229}
230
Lars-Peter Clausen19b63172013-05-14 22:19:53 +0200231static struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
Barry Song01e2ab22009-07-27 18:06:39 +0800232 .open = bf5xx_pcm_open,
233 .ioctl = snd_pcm_lib_ioctl,
234 .hw_params = bf5xx_pcm_hw_params,
235 .hw_free = bf5xx_pcm_hw_free,
236 .prepare = bf5xx_pcm_prepare,
237 .trigger = bf5xx_pcm_trigger,
238 .pointer = bf5xx_pcm_pointer,
239 .copy = bf5xx_pcm_copy,
240 .silence = bf5xx_pcm_silence,
241};
242
243static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
244{
245 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
246 struct snd_dma_buffer *buf = &substream->dma_buffer;
247 size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
248
249 buf->dev.type = SNDRV_DMA_TYPE_DEV;
250 buf->dev.dev = pcm->card->dev;
251 buf->private_data = NULL;
252 buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
253 &buf->addr, GFP_KERNEL);
254 if (!buf->area) {
Joe Perches2f1ff662010-01-31 12:02:12 -0800255 pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
Barry Song01e2ab22009-07-27 18:06:39 +0800256 return -ENOMEM;
257 }
258 buf->bytes = size;
259
Barry Song01e2ab22009-07-27 18:06:39 +0800260 return 0;
261}
262
263static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
264{
265 struct snd_pcm_substream *substream;
266 struct snd_dma_buffer *buf;
267 int stream;
268
269 for (stream = 0; stream < 2; stream++) {
270 substream = pcm->streams[stream].substream;
271 if (!substream)
272 continue;
273
274 buf = &substream->dma_buffer;
275 if (!buf->area)
276 continue;
277 dma_free_coherent(NULL, buf->bytes, buf->area, 0);
278 buf->area = NULL;
279 }
Barry Song01e2ab22009-07-27 18:06:39 +0800280}
281
Barry Song3a39f832009-07-29 15:03:16 +0800282static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
Barry Song01e2ab22009-07-27 18:06:39 +0800283
Liam Girdwood552d1ef2011-06-07 16:08:33 +0100284static int bf5xx_pcm_tdm_new(struct snd_soc_pcm_runtime *rtd)
Barry Song01e2ab22009-07-27 18:06:39 +0800285{
Liam Girdwood552d1ef2011-06-07 16:08:33 +0100286 struct snd_card *card = rtd->card->snd_card;
Liam Girdwood552d1ef2011-06-07 16:08:33 +0100287 struct snd_pcm *pcm = rtd->pcm;
Barry Song01e2ab22009-07-27 18:06:39 +0800288 int ret = 0;
289
290 if (!card->dev->dma_mask)
291 card->dev->dma_mask = &bf5xx_pcm_dmamask;
292 if (!card->dev->coherent_dma_mask)
Barry Song3a39f832009-07-29 15:03:16 +0800293 card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
Barry Song01e2ab22009-07-27 18:06:39 +0800294
Joachim Eastwood25e9e752012-01-01 01:58:44 +0100295 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
Barry Song01e2ab22009-07-27 18:06:39 +0800296 ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
297 SNDRV_PCM_STREAM_PLAYBACK);
298 if (ret)
299 goto out;
300 }
301
Joachim Eastwood25e9e752012-01-01 01:58:44 +0100302 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
Barry Song01e2ab22009-07-27 18:06:39 +0800303 ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
304 SNDRV_PCM_STREAM_CAPTURE);
305 if (ret)
306 goto out;
307 }
308out:
309 return ret;
310}
311
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000312static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = {
313 .ops = &bf5xx_pcm_tdm_ops,
Barry Song01e2ab22009-07-27 18:06:39 +0800314 .pcm_new = bf5xx_pcm_tdm_new,
315 .pcm_free = bf5xx_pcm_free_dma_buffers,
316};
Barry Song01e2ab22009-07-27 18:06:39 +0800317
Bill Pembertondca66da2012-12-07 09:26:13 -0500318static int bf5xx_soc_platform_probe(struct platform_device *pdev)
Barry Song01e2ab22009-07-27 18:06:39 +0800319{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000320 return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
Barry Song01e2ab22009-07-27 18:06:39 +0800321}
Barry Song01e2ab22009-07-27 18:06:39 +0800322
Bill Pembertondca66da2012-12-07 09:26:13 -0500323static int bf5xx_soc_platform_remove(struct platform_device *pdev)
Barry Song01e2ab22009-07-27 18:06:39 +0800324{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000325 snd_soc_unregister_platform(&pdev->dev);
326 return 0;
Barry Song01e2ab22009-07-27 18:06:39 +0800327}
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000328
329static struct platform_driver bfin_tdm_driver = {
330 .driver = {
Mike Frysingerbfe4ee02011-03-28 01:45:09 -0400331 .name = "bfin-tdm-pcm-audio",
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000332 .owner = THIS_MODULE,
333 },
334
335 .probe = bf5xx_soc_platform_probe,
Bill Pembertondca66da2012-12-07 09:26:13 -0500336 .remove = bf5xx_soc_platform_remove,
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000337};
338
Axel Linfb802972011-11-24 14:44:52 +0800339module_platform_driver(bfin_tdm_driver);
Barry Song01e2ab22009-07-27 18:06:39 +0800340
341MODULE_AUTHOR("Barry Song");
342MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
343MODULE_LICENSE("GPL");