• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * omap-pcm.c  --  ALSA PCM interface for the OMAP SoC
3  *
4  * Copyright (C) 2008 Nokia Corporation
5  *
6  * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
7  *          Peter Ujfalusi <peter.ujfalusi@ti.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * version 2 as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24 
25 #include <linux/dma-mapping.h>
26 #include <linux/slab.h>
27 #include <linux/module.h>
28 #include <linux/omap-dma.h>
29 #include <sound/core.h>
30 #include <sound/pcm.h>
31 #include <sound/pcm_params.h>
32 #include <sound/dmaengine_pcm.h>
33 #include <sound/soc.h>
34 #include <sound/omap-pcm.h>
35 
36 #ifdef CONFIG_ARCH_OMAP1
37 #define pcm_omap1510()	cpu_is_omap1510()
38 #else
39 #define pcm_omap1510()	0
40 #endif
41 
42 static struct snd_pcm_hardware omap_pcm_hardware = {
43 	.info			= SNDRV_PCM_INFO_MMAP |
44 				  SNDRV_PCM_INFO_MMAP_VALID |
45 				  SNDRV_PCM_INFO_INTERLEAVED |
46 				  SNDRV_PCM_INFO_PAUSE |
47 				  SNDRV_PCM_INFO_RESUME |
48 				  SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
49 	.period_bytes_min	= 32,
50 	.period_bytes_max	= 64 * 1024,
51 	.periods_min		= 2,
52 	.periods_max		= 255,
53 	.buffer_bytes_max	= 128 * 1024,
54 };
55 
56 /* sDMA supports only 1, 2, and 4 byte transfer elements. */
omap_pcm_limit_supported_formats(void)57 static void omap_pcm_limit_supported_formats(void)
58 {
59 	int i;
60 
61 	for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) {
62 		switch (snd_pcm_format_physical_width(i)) {
63 		case 8:
64 		case 16:
65 		case 32:
66 			omap_pcm_hardware.formats |= (1LL << i);
67 			break;
68 		default:
69 			break;
70 		}
71 	}
72 }
73 
74 /* this may get called several times by oss emulation */
omap_pcm_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)75 static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
76 			      struct snd_pcm_hw_params *params)
77 {
78 	struct snd_pcm_runtime *runtime = substream->runtime;
79 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
80 	struct omap_pcm_dma_data *dma_data;
81 	struct dma_slave_config config;
82 	struct dma_chan *chan;
83 	int err = 0;
84 
85 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
86 
87 	/* return if this is a bufferless transfer e.g.
88 	 * codec <--> BT codec or GSM modem -- lg FIXME */
89 	if (!dma_data)
90 		return 0;
91 
92 	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
93 	runtime->dma_bytes = params_buffer_bytes(params);
94 
95 	chan = snd_dmaengine_pcm_get_chan(substream);
96 	if (!chan)
97 		return -EINVAL;
98 
99 	/* fills in addr_width and direction */
100 	err = snd_hwparams_to_dma_slave_config(substream, params, &config);
101 	if (err)
102 		return err;
103 
104 	snd_dmaengine_pcm_set_config_from_dai_data(substream,
105 			snd_soc_dai_get_dma_data(rtd->cpu_dai, substream),
106 			&config);
107 
108 	return dmaengine_slave_config(chan, &config);
109 }
110 
omap_pcm_hw_free(struct snd_pcm_substream * substream)111 static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
112 {
113 	snd_pcm_set_runtime_buffer(substream, NULL);
114 	return 0;
115 }
116 
omap_pcm_pointer(struct snd_pcm_substream * substream)117 static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
118 {
119 	snd_pcm_uframes_t offset;
120 
121 	if (pcm_omap1510())
122 		offset = snd_dmaengine_pcm_pointer_no_residue(substream);
123 	else
124 		offset = snd_dmaengine_pcm_pointer(substream);
125 
126 	return offset;
127 }
128 
omap_pcm_open(struct snd_pcm_substream * substream)129 static int omap_pcm_open(struct snd_pcm_substream *substream)
130 {
131 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
132 	struct snd_dmaengine_dai_dma_data *dma_data;
133 	int ret;
134 
135 	snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
136 
137 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
138 
139 	/* DT boot: filter_data is the DMA name */
140 	if (rtd->cpu_dai->dev->of_node) {
141 		struct dma_chan *chan;
142 
143 		chan = dma_request_slave_channel(rtd->cpu_dai->dev,
144 						 dma_data->filter_data);
145 		ret = snd_dmaengine_pcm_open(substream, chan);
146 	} else {
147 		ret = snd_dmaengine_pcm_open_request_chan(substream,
148 							  omap_dma_filter_fn,
149 							  dma_data->filter_data);
150 	}
151 	return ret;
152 }
153 
omap_pcm_mmap(struct snd_pcm_substream * substream,struct vm_area_struct * vma)154 static int omap_pcm_mmap(struct snd_pcm_substream *substream,
155 	struct vm_area_struct *vma)
156 {
157 	struct snd_pcm_runtime *runtime = substream->runtime;
158 
159 	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
160 				     runtime->dma_area,
161 				     runtime->dma_addr,
162 				     runtime->dma_bytes);
163 }
164 
165 static struct snd_pcm_ops omap_pcm_ops = {
166 	.open		= omap_pcm_open,
167 	.close		= snd_dmaengine_pcm_close_release_chan,
168 	.ioctl		= snd_pcm_lib_ioctl,
169 	.hw_params	= omap_pcm_hw_params,
170 	.hw_free	= omap_pcm_hw_free,
171 	.trigger	= snd_dmaengine_pcm_trigger,
172 	.pointer	= omap_pcm_pointer,
173 	.mmap		= omap_pcm_mmap,
174 };
175 
omap_pcm_preallocate_dma_buffer(struct snd_pcm * pcm,int stream)176 static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
177 	int stream)
178 {
179 	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
180 	struct snd_dma_buffer *buf = &substream->dma_buffer;
181 	size_t size = omap_pcm_hardware.buffer_bytes_max;
182 
183 	buf->dev.type = SNDRV_DMA_TYPE_DEV;
184 	buf->dev.dev = pcm->card->dev;
185 	buf->private_data = NULL;
186 	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
187 					   &buf->addr, GFP_KERNEL);
188 	if (!buf->area)
189 		return -ENOMEM;
190 
191 	buf->bytes = size;
192 	return 0;
193 }
194 
omap_pcm_free_dma_buffers(struct snd_pcm * pcm)195 static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
196 {
197 	struct snd_pcm_substream *substream;
198 	struct snd_dma_buffer *buf;
199 	int stream;
200 
201 	for (stream = 0; stream < 2; stream++) {
202 		substream = pcm->streams[stream].substream;
203 		if (!substream)
204 			continue;
205 
206 		buf = &substream->dma_buffer;
207 		if (!buf->area)
208 			continue;
209 
210 		dma_free_writecombine(pcm->card->dev, buf->bytes,
211 				      buf->area, buf->addr);
212 		buf->area = NULL;
213 	}
214 }
215 
omap_pcm_new(struct snd_soc_pcm_runtime * rtd)216 static int omap_pcm_new(struct snd_soc_pcm_runtime *rtd)
217 {
218 	struct snd_card *card = rtd->card->snd_card;
219 	struct snd_pcm *pcm = rtd->pcm;
220 	int ret;
221 
222 	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
223 	if (ret)
224 		return ret;
225 
226 	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
227 		ret = omap_pcm_preallocate_dma_buffer(pcm,
228 			SNDRV_PCM_STREAM_PLAYBACK);
229 		if (ret)
230 			goto out;
231 	}
232 
233 	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
234 		ret = omap_pcm_preallocate_dma_buffer(pcm,
235 			SNDRV_PCM_STREAM_CAPTURE);
236 		if (ret)
237 			goto out;
238 	}
239 
240 out:
241 	/* free preallocated buffers in case of error */
242 	if (ret)
243 		omap_pcm_free_dma_buffers(pcm);
244 
245 	return ret;
246 }
247 
248 static struct snd_soc_platform_driver omap_soc_platform = {
249 	.ops		= &omap_pcm_ops,
250 	.pcm_new	= omap_pcm_new,
251 	.pcm_free	= omap_pcm_free_dma_buffers,
252 };
253 
omap_pcm_platform_register(struct device * dev)254 int omap_pcm_platform_register(struct device *dev)
255 {
256 	omap_pcm_limit_supported_formats();
257 	return devm_snd_soc_register_platform(dev, &omap_soc_platform);
258 }
259 EXPORT_SYMBOL_GPL(omap_pcm_platform_register);
260 
261 MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
262 MODULE_DESCRIPTION("OMAP PCM DMA module");
263 MODULE_LICENSE("GPL");
264