diff options
author | Cliff Cai <cliff.cai@analog.com> | 2008-09-05 06:21:38 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2008-09-09 03:11:20 -0400 |
commit | 88740da15c3ff9d22dddfb5bc4b9fb7161cf3373 (patch) | |
tree | 73ec2af67204d9ac2b4f2ccb61752c10ee00d75d /sound/soc/blackfin/bf5xx-i2s-pcm.c | |
parent | f2028623f2b14bdd3daeb0873a16cab0dc4050c4 (diff) |
sound: ASoC: Blackfin: DMA Driver for I2S sound chip
Signed-off-by: Cliff Cai <cliff.cai@analog.com>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/soc/blackfin/bf5xx-i2s-pcm.c')
-rw-r--r-- | sound/soc/blackfin/bf5xx-i2s-pcm.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c new file mode 100644 index 000000000000..61fccf925192 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c | |||
@@ -0,0 +1,288 @@ | |||
1 | /* | ||
2 | * File: sound/soc/blackfin/bf5xx-i2s-pcm.c | ||
3 | * Author: Cliff Cai <Cliff.Cai@analog.com> | ||
4 | * | ||
5 | * Created: Tue June 06 2008 | ||
6 | * Description: DMA driver for i2s codec | ||
7 | * | ||
8 | * Modified: | ||
9 | * Copyright 2008 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/slab.h> | ||
33 | #include <linux/dma-mapping.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-i2s-pcm.h" | ||
43 | #include "bf5xx-i2s.h" | ||
44 | #include "bf5xx-sport.h" | ||
45 | |||
46 | static void bf5xx_dma_irq(void *data) | ||
47 | { | ||
48 | struct snd_pcm_substream *pcm = data; | ||
49 | snd_pcm_period_elapsed(pcm); | ||
50 | } | ||
51 | |||
52 | static const struct snd_pcm_hardware bf5xx_pcm_hardware = { | ||
53 | .info = SNDRV_PCM_INFO_INTERLEAVED | | ||
54 | SNDRV_PCM_INFO_MMAP | | ||
55 | SNDRV_PCM_INFO_MMAP_VALID | | ||
56 | SNDRV_PCM_INFO_BLOCK_TRANSFER, | ||
57 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | ||
58 | SNDRV_PCM_FMTBIT_S24_LE | | ||
59 | SNDRV_PCM_FMTBIT_S32_LE, | ||
60 | .period_bytes_min = 32, | ||
61 | .period_bytes_max = 0x10000, | ||
62 | .periods_min = 1, | ||
63 | .periods_max = PAGE_SIZE/32, | ||
64 | .buffer_bytes_max = 0x20000, /* 128 kbytes */ | ||
65 | .fifo_size = 16, | ||
66 | }; | ||
67 | |||
68 | static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
69 | struct snd_pcm_hw_params *params) | ||
70 | { | ||
71 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max; | ||
72 | snd_pcm_lib_malloc_pages(substream, size); | ||
73 | |||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) | ||
78 | { | ||
79 | snd_pcm_lib_free_pages(substream); | ||
80 | |||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) | ||
85 | { | ||
86 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
87 | struct sport_device *sport = runtime->private_data; | ||
88 | int period_bytes = frames_to_bytes(runtime, runtime->period_size); | ||
89 | |||
90 | pr_debug("%s enter\n", __func__); | ||
91 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
92 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); | ||
93 | sport_config_tx_dma(sport, runtime->dma_area, | ||
94 | runtime->periods, period_bytes); | ||
95 | } else { | ||
96 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); | ||
97 | sport_config_rx_dma(sport, runtime->dma_area, | ||
98 | runtime->periods, period_bytes); | ||
99 | } | ||
100 | |||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
105 | { | ||
106 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
107 | struct sport_device *sport = runtime->private_data; | ||
108 | int ret = 0; | ||
109 | |||
110 | pr_debug("%s enter\n", __func__); | ||
111 | switch (cmd) { | ||
112 | case SNDRV_PCM_TRIGGER_START: | ||
113 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
114 | sport_tx_start(sport); | ||
115 | else | ||
116 | sport_rx_start(sport); | ||
117 | break; | ||
118 | case SNDRV_PCM_TRIGGER_STOP: | ||
119 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
120 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
121 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
122 | sport_tx_stop(sport); | ||
123 | else | ||
124 | sport_rx_stop(sport); | ||
125 | break; | ||
126 | default: | ||
127 | ret = -EINVAL; | ||
128 | } | ||
129 | |||
130 | return ret; | ||
131 | } | ||
132 | |||
133 | static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) | ||
134 | { | ||
135 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
136 | struct sport_device *sport = runtime->private_data; | ||
137 | unsigned int diff; | ||
138 | snd_pcm_uframes_t frames; | ||
139 | pr_debug("%s enter\n", __func__); | ||
140 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
141 | diff = sport_curr_offset_tx(sport); | ||
142 | frames = bytes_to_frames(substream->runtime, diff); | ||
143 | } else { | ||
144 | diff = sport_curr_offset_rx(sport); | ||
145 | frames = bytes_to_frames(substream->runtime, diff); | ||
146 | } | ||
147 | return frames; | ||
148 | } | ||
149 | |||
150 | static int bf5xx_pcm_open(struct snd_pcm_substream *substream) | ||
151 | { | ||
152 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
153 | int ret; | ||
154 | |||
155 | pr_debug("%s enter\n", __func__); | ||
156 | snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); | ||
157 | |||
158 | ret = snd_pcm_hw_constraint_integer(runtime, \ | ||
159 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
160 | if (ret < 0) | ||
161 | goto out; | ||
162 | |||
163 | if (sport_handle != NULL) | ||
164 | runtime->private_data = sport_handle; | ||
165 | else { | ||
166 | pr_err("sport_handle is NULL\n"); | ||
167 | return -1; | ||
168 | } | ||
169 | return 0; | ||
170 | |||
171 | out: | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, | ||
176 | struct vm_area_struct *vma) | ||
177 | { | ||
178 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
179 | size_t size = vma->vm_end - vma->vm_start; | ||
180 | vma->vm_start = (unsigned long)runtime->dma_area; | ||
181 | vma->vm_end = vma->vm_start + size; | ||
182 | vma->vm_flags |= VM_SHARED; | ||
183 | |||
184 | return 0 ; | ||
185 | } | ||
186 | |||
187 | struct snd_pcm_ops bf5xx_pcm_i2s_ops = { | ||
188 | .open = bf5xx_pcm_open, | ||
189 | .ioctl = snd_pcm_lib_ioctl, | ||
190 | .hw_params = bf5xx_pcm_hw_params, | ||
191 | .hw_free = bf5xx_pcm_hw_free, | ||
192 | .prepare = bf5xx_pcm_prepare, | ||
193 | .trigger = bf5xx_pcm_trigger, | ||
194 | .pointer = bf5xx_pcm_pointer, | ||
195 | .mmap = bf5xx_pcm_mmap, | ||
196 | }; | ||
197 | |||
198 | static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
199 | { | ||
200 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
201 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
202 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max; | ||
203 | |||
204 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
205 | buf->dev.dev = pcm->card->dev; | ||
206 | buf->private_data = NULL; | ||
207 | buf->area = dma_alloc_coherent(pcm->card->dev, size, | ||
208 | &buf->addr, GFP_KERNEL); | ||
209 | if (!buf->area) { | ||
210 | pr_err("Failed to allocate dma memory \ | ||
211 | Please increase uncached DMA memory region\n"); | ||
212 | return -ENOMEM; | ||
213 | } | ||
214 | buf->bytes = size; | ||
215 | |||
216 | pr_debug("%s, area:%p, size:0x%08lx\n", __func__, | ||
217 | buf->area, buf->bytes); | ||
218 | |||
219 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
220 | sport_handle->tx_buf = buf->area; | ||
221 | else | ||
222 | sport_handle->rx_buf = buf->area; | ||
223 | |||
224 | return 0; | ||
225 | } | ||
226 | |||
227 | static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | ||
228 | { | ||
229 | struct snd_pcm_substream *substream; | ||
230 | struct snd_dma_buffer *buf; | ||
231 | int stream; | ||
232 | |||
233 | for (stream = 0; stream < 2; stream++) { | ||
234 | substream = pcm->streams[stream].substream; | ||
235 | if (!substream) | ||
236 | continue; | ||
237 | |||
238 | buf = &substream->dma_buffer; | ||
239 | if (!buf->area) | ||
240 | continue; | ||
241 | dma_free_coherent(NULL, buf->bytes, buf->area, 0); | ||
242 | buf->area = NULL; | ||
243 | } | ||
244 | if (sport_handle) | ||
245 | sport_done(sport_handle); | ||
246 | } | ||
247 | |||
248 | static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; | ||
249 | |||
250 | int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai, | ||
251 | struct snd_pcm *pcm) | ||
252 | { | ||
253 | int ret = 0; | ||
254 | |||
255 | pr_debug("%s enter\n", __func__); | ||
256 | if (!card->dev->dma_mask) | ||
257 | card->dev->dma_mask = &bf5xx_pcm_dmamask; | ||
258 | if (!card->dev->coherent_dma_mask) | ||
259 | card->dev->coherent_dma_mask = DMA_32BIT_MASK; | ||
260 | |||
261 | if (dai->playback.channels_min) { | ||
262 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | ||
263 | SNDRV_PCM_STREAM_PLAYBACK); | ||
264 | if (ret) | ||
265 | goto out; | ||
266 | } | ||
267 | |||
268 | if (dai->capture.channels_min) { | ||
269 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | ||
270 | SNDRV_PCM_STREAM_CAPTURE); | ||
271 | if (ret) | ||
272 | goto out; | ||
273 | } | ||
274 | out: | ||
275 | return ret; | ||
276 | } | ||
277 | |||
278 | struct snd_soc_platform bf5xx_i2s_soc_platform = { | ||
279 | .name = "bf5xx-audio", | ||
280 | .pcm_ops = &bf5xx_pcm_i2s_ops, | ||
281 | .pcm_new = bf5xx_pcm_i2s_new, | ||
282 | .pcm_free = bf5xx_pcm_free_dma_buffers, | ||
283 | }; | ||
284 | EXPORT_SYMBOL_GPL(bf5xx_i2s_soc_platform); | ||
285 | |||
286 | MODULE_AUTHOR("Cliff Cai"); | ||
287 | MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); | ||
288 | MODULE_LICENSE("GPL"); | ||