• 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 	memset(&config, 0x00, sizeof(config));
86 
87 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
88 
89 	/* return if this is a bufferless transfer e.g.
90 	 * codec <--> BT codec or GSM modem -- lg FIXME */
91 	if (!dma_data)
92 		return 0;
93 
94 	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
95 	runtime->dma_bytes = params_buffer_bytes(params);
96 
97 	chan = snd_dmaengine_pcm_get_chan(substream);
98 	if (!chan)
99 		return -EINVAL;
100 
101 	/* fills in addr_width and direction */
102 	err = snd_hwparams_to_dma_slave_config(substream, params, &config);
103 	if (err)
104 		return err;
105 
106 	snd_dmaengine_pcm_set_config_from_dai_data(substream,
107 			snd_soc_dai_get_dma_data(rtd->cpu_dai, substream),
108 			&config);
109 
110 	return dmaengine_slave_config(chan, &config);
111 }
112 
omap_pcm_hw_free(struct snd_pcm_substream * substream)113 static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
114 {
115 	snd_pcm_set_runtime_buffer(substream, NULL);
116 	return 0;
117 }
118 
omap_pcm_pointer(struct snd_pcm_substream * substream)119 static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
120 {
121 	snd_pcm_uframes_t offset;
122 
123 	if (pcm_omap1510())
124 		offset = snd_dmaengine_pcm_pointer_no_residue(substream);
125 	else
126 		offset = snd_dmaengine_pcm_pointer(substream);
127 
128 	return offset;
129 }
130 
omap_pcm_open(struct snd_pcm_substream * substream)131 static int omap_pcm_open(struct snd_pcm_substream *substream)
132 {
133 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
134 	struct snd_dmaengine_dai_dma_data *dma_data;
135 	int ret;
136 
137 	snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
138 
139 	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
140 
141 	/* DT boot: filter_data is the DMA name */
142 	if (rtd->cpu_dai->dev->of_node) {
143 		struct dma_chan *chan;
144 
145 		chan = dma_request_slave_channel(rtd->cpu_dai->dev,
146 						 dma_data->filter_data);
147 		ret = snd_dmaengine_pcm_open(substream, chan);
148 	} else {
149 		ret = snd_dmaengine_pcm_open_request_chan(substream,
150 							  omap_dma_filter_fn,
151 							  dma_data->filter_data);
152 	}
153 	return ret;
154 }
155 
omap_pcm_mmap(struct snd_pcm_substream * substream,struct vm_area_struct * vma)156 static int omap_pcm_mmap(struct snd_pcm_substream *substream,
157 	struct vm_area_struct *vma)
158 {
159 	struct snd_pcm_runtime *runtime = substream->runtime;
160 
161 	return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area,
162 			   runtime->dma_addr, runtime->dma_bytes);
163 }
164 
165 static const 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_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL);
187 	if (!buf->area)
188 		return -ENOMEM;
189 
190 	buf->bytes = size;
191 	return 0;
192 }
193 
omap_pcm_free_dma_buffers(struct snd_pcm * pcm)194 static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
195 {
196 	struct snd_pcm_substream *substream;
197 	struct snd_dma_buffer *buf;
198 	int stream;
199 
200 	for (stream = 0; stream < 2; stream++) {
201 		substream = pcm->streams[stream].substream;
202 		if (!substream)
203 			continue;
204 
205 		buf = &substream->dma_buffer;
206 		if (!buf->area)
207 			continue;
208 
209 		dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr);
210 		buf->area = NULL;
211 	}
212 }
213 
omap_pcm_new(struct snd_soc_pcm_runtime * rtd)214 static int omap_pcm_new(struct snd_soc_pcm_runtime *rtd)
215 {
216 	struct snd_card *card = rtd->card->snd_card;
217 	struct snd_pcm *pcm = rtd->pcm;
218 	int ret;
219 
220 	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
221 	if (ret)
222 		return ret;
223 
224 	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
225 		ret = omap_pcm_preallocate_dma_buffer(pcm,
226 			SNDRV_PCM_STREAM_PLAYBACK);
227 		if (ret)
228 			goto out;
229 	}
230 
231 	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
232 		ret = omap_pcm_preallocate_dma_buffer(pcm,
233 			SNDRV_PCM_STREAM_CAPTURE);
234 		if (ret)
235 			goto out;
236 	}
237 
238 out:
239 	/* free preallocated buffers in case of error */
240 	if (ret)
241 		omap_pcm_free_dma_buffers(pcm);
242 
243 	return ret;
244 }
245 
246 static const struct snd_soc_platform_driver omap_soc_platform = {
247 	.ops		= &omap_pcm_ops,
248 	.pcm_new	= omap_pcm_new,
249 	.pcm_free	= omap_pcm_free_dma_buffers,
250 };
251 
omap_pcm_platform_register(struct device * dev)252 int omap_pcm_platform_register(struct device *dev)
253 {
254 	omap_pcm_limit_supported_formats();
255 	return devm_snd_soc_register_platform(dev, &omap_soc_platform);
256 }
257 EXPORT_SYMBOL_GPL(omap_pcm_platform_register);
258 
259 MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>");
260 MODULE_DESCRIPTION("OMAP PCM DMA module");
261 MODULE_LICENSE("GPL");
262