Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
bf5xx-tdm-pcm.c
Go to the documentation of this file.
1 /*
2  * File: sound/soc/blackfin/bf5xx-tdm-pcm.c
3  * Author: Barry Song <[email protected]>
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 
51 static void bf5xx_dma_irq(void *data)
52 {
53  struct snd_pcm_substream *pcm = data;
55 }
56 
57 static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
62  .channels_min = 2,
63  .channels_max = 8,
69 };
70 
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 
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 
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 
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) {
118  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
119  sport_tx_start(sport);
120  else
121  sport_rx_start(sport);
122  break;
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 
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 
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,
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 
187 static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
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 
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 
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 
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 
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 
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,
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,
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 
320 static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
321 {
322  return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
323 }
324 
325 static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
326 {
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 = __devexit_p(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");