Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
mmp-pcm.c
Go to the documentation of this file.
1 /*
2  * linux/sound/soc/pxa/mmp-pcm.c
3  *
4  * Copyright (C) 2011 Marvell International Ltd.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  */
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/platform_device.h>
15 #include <linux/slab.h>
16 #include <linux/dma-mapping.h>
17 #include <linux/dmaengine.h>
20 #include <sound/pxa2xx-lib.h>
21 #include <sound/core.h>
22 #include <sound/pcm.h>
23 #include <sound/pcm_params.h>
24 #include <sound/soc.h>
25 #include <sound/dmaengine_pcm.h>
26 
27 struct mmp_dma_data {
28  int ssp_id;
29  struct resource *dma_res;
30 };
31 
32 #define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
33  SNDRV_PCM_INFO_MMAP_VALID | \
34  SNDRV_PCM_INFO_INTERLEAVED | \
35  SNDRV_PCM_INFO_PAUSE | \
36  SNDRV_PCM_INFO_RESUME)
37 
38 #define MMP_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
39  SNDRV_PCM_FMTBIT_S24_LE | \
40  SNDRV_PCM_FMTBIT_S32_LE)
41 
42 static struct snd_pcm_hardware mmp_pcm_hardware[] = {
43  {
44  .info = MMP_PCM_INFO,
45  .formats = MMP_PCM_FORMATS,
46  .period_bytes_min = 1024,
47  .period_bytes_max = 2048,
48  .periods_min = 2,
49  .periods_max = 32,
50  .buffer_bytes_max = 4096,
51  .fifo_size = 32,
52  },
53  {
54  .info = MMP_PCM_INFO,
55  .formats = MMP_PCM_FORMATS,
56  .period_bytes_min = 1024,
57  .period_bytes_max = 2048,
58  .periods_min = 2,
59  .periods_max = 32,
60  .buffer_bytes_max = 4096,
61  .fifo_size = 32,
62  },
63 };
64 
65 static int mmp_pcm_hw_params(struct snd_pcm_substream *substream,
66  struct snd_pcm_hw_params *params)
67 {
68  struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
69  struct snd_soc_pcm_runtime *rtd = substream->private_data;
70  struct pxa2xx_pcm_dma_params *dma_params;
72  int ret;
73 
74  dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
75  if (!dma_params)
76  return 0;
77 
78  ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config);
79  if (ret)
80  return ret;
81 
82  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
83  slave_config.dst_addr = dma_params->dev_addr;
84  slave_config.dst_maxburst = 4;
85  } else {
86  slave_config.src_addr = dma_params->dev_addr;
87  slave_config.src_maxburst = 4;
88  }
89 
90  ret = dmaengine_slave_config(chan, &slave_config);
91  if (ret)
92  return ret;
93 
94  snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
95 
96  return 0;
97 }
98 
99 static bool filter(struct dma_chan *chan, void *param)
100 {
101  struct mmp_dma_data *dma_data = param;
102  bool found = false;
103  char *devname;
104 
105  devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
106  dma_data->ssp_id);
107  if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
108  (chan->chan_id == dma_data->dma_res->start)) {
109  found = true;
110  }
111 
112  kfree(devname);
113  return found;
114 }
115 
116 static int mmp_pcm_open(struct snd_pcm_substream *substream)
117 {
118  struct snd_soc_pcm_runtime *rtd = substream->private_data;
119  struct platform_device *pdev = to_platform_device(rtd->platform->dev);
120  struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
121  struct mmp_dma_data *dma_data;
122  struct resource *r;
123  int ret;
124 
125  r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream);
126  if (!r)
127  return -EBUSY;
128 
130  &mmp_pcm_hardware[substream->stream]);
131  dma_data = devm_kzalloc(&pdev->dev,
132  sizeof(struct mmp_dma_data), GFP_KERNEL);
133  if (dma_data == NULL)
134  return -ENOMEM;
135 
136  dma_data->dma_res = r;
137  dma_data->ssp_id = cpu_dai->id;
138 
139  ret = snd_dmaengine_pcm_open(substream, filter, dma_data);
140  if (ret) {
141  devm_kfree(&pdev->dev, dma_data);
142  return ret;
143  }
144 
145  snd_dmaengine_pcm_set_data(substream, dma_data);
146  return 0;
147 }
148 
149 static int mmp_pcm_close(struct snd_pcm_substream *substream)
150 {
151  struct mmp_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
152  struct snd_soc_pcm_runtime *rtd = substream->private_data;
153  struct platform_device *pdev = to_platform_device(rtd->platform->dev);
154 
155  snd_dmaengine_pcm_close(substream);
156  devm_kfree(&pdev->dev, dma_data);
157  return 0;
158 }
159 
160 static int mmp_pcm_mmap(struct snd_pcm_substream *substream,
161  struct vm_area_struct *vma)
162 {
163  struct snd_pcm_runtime *runtime = substream->runtime;
164  unsigned long off = vma->vm_pgoff;
165 
167  return remap_pfn_range(vma, vma->vm_start,
168  __phys_to_pfn(runtime->dma_addr) + off,
169  vma->vm_end - vma->vm_start, vma->vm_page_prot);
170 }
171 
173  .open = mmp_pcm_open,
174  .close = mmp_pcm_close,
175  .ioctl = snd_pcm_lib_ioctl,
176  .hw_params = mmp_pcm_hw_params,
177  .trigger = snd_dmaengine_pcm_trigger,
178  .pointer = snd_dmaengine_pcm_pointer,
179  .mmap = mmp_pcm_mmap,
180 };
181 
182 static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm)
183 {
184  struct snd_pcm_substream *substream;
185  struct snd_dma_buffer *buf;
186  int stream;
187  struct gen_pool *gpool;
188 
189  gpool = sram_get_gpool("asram");
190  if (!gpool)
191  return;
192 
193  for (stream = 0; stream < 2; stream++) {
194  size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
195 
196  substream = pcm->streams[stream].substream;
197  if (!substream)
198  continue;
199 
200  buf = &substream->dma_buffer;
201  if (!buf->area)
202  continue;
203  gen_pool_free(gpool, (unsigned long)buf->area, size);
204  buf->area = NULL;
205  }
206 
207  return;
208 }
209 
210 static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
211  int stream)
212 {
213  struct snd_dma_buffer *buf = &substream->dma_buffer;
214  size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
215  struct gen_pool *gpool;
216 
217  buf->dev.type = SNDRV_DMA_TYPE_DEV;
218  buf->dev.dev = substream->pcm->card->dev;
219  buf->private_data = NULL;
220 
221  gpool = sram_get_gpool("asram");
222  if (!gpool)
223  return -ENOMEM;
224 
225  buf->area = (unsigned char *)gen_pool_alloc(gpool, size);
226  if (!buf->area)
227  return -ENOMEM;
228  buf->addr = gen_pool_virt_to_phys(gpool, (unsigned long)buf->area);
229  buf->bytes = size;
230  return 0;
231 }
232 
234 {
235  struct snd_pcm_substream *substream;
236  struct snd_pcm *pcm = rtd->pcm;
237  int ret = 0, stream;
238 
239  for (stream = 0; stream < 2; stream++) {
240  substream = pcm->streams[stream].substream;
241 
242  ret = mmp_pcm_preallocate_dma_buffer(substream, stream);
243  if (ret)
244  goto err;
245  }
246 
247  return 0;
248 
249 err:
250  mmp_pcm_free_dma_buffers(pcm);
251  return ret;
252 }
253 
255  .ops = &mmp_pcm_ops,
256  .pcm_new = mmp_pcm_new,
257  .pcm_free = mmp_pcm_free_dma_buffers,
258 };
259 
260 static __devinit int mmp_pcm_probe(struct platform_device *pdev)
261 {
262  struct mmp_audio_platdata *pdata = pdev->dev.platform_data;
263 
264  if (pdata) {
265  mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max =
266  pdata->buffer_max_playback;
267  mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max =
268  pdata->period_max_playback;
269  mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max =
270  pdata->buffer_max_capture;
271  mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max =
272  pdata->period_max_capture;
273  }
274  return snd_soc_register_platform(&pdev->dev, &mmp_soc_platform);
275 }
276 
277 static int __devexit mmp_pcm_remove(struct platform_device *pdev)
278 {
280  return 0;
281 }
282 
283 static struct platform_driver mmp_pcm_driver = {
284  .driver = {
285  .name = "mmp-pcm-audio",
286  .owner = THIS_MODULE,
287  },
288 
289  .probe = mmp_pcm_probe,
290  .remove = __devexit_p(mmp_pcm_remove),
291 };
292 
293 module_platform_driver(mmp_pcm_driver);
294 
295 MODULE_AUTHOR("Leo Yan <[email protected]>");
296 MODULE_DESCRIPTION("MMP Soc Audio DMA module");
297 MODULE_LICENSE("GPL");