diff options
Diffstat (limited to 'sound/arm/pxa2xx-pcm-lib.c')
-rw-r--r-- | sound/arm/pxa2xx-pcm-lib.c | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/sound/arm/pxa2xx-pcm-lib.c b/sound/arm/pxa2xx-pcm-lib.c new file mode 100644 index 000000000000..1c93eb77cb99 --- /dev/null +++ b/sound/arm/pxa2xx-pcm-lib.c | |||
@@ -0,0 +1,278 @@ | |||
1 | /* | ||
2 | * This program is free software; you can redistribute it and/or modify | ||
3 | * it under the terms of the GNU General Public License version 2 as | ||
4 | * published by the Free Software Foundation. | ||
5 | */ | ||
6 | |||
7 | #include <linux/module.h> | ||
8 | #include <linux/dma-mapping.h> | ||
9 | |||
10 | #include <sound/core.h> | ||
11 | #include <sound/pcm.h> | ||
12 | #include <sound/pcm_params.h> | ||
13 | #include <sound/pxa2xx-lib.h> | ||
14 | |||
15 | #include <asm/dma.h> | ||
16 | #include <mach/pxa-regs.h> | ||
17 | |||
18 | #include "pxa2xx-pcm.h" | ||
19 | |||
20 | static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { | ||
21 | .info = SNDRV_PCM_INFO_MMAP | | ||
22 | SNDRV_PCM_INFO_MMAP_VALID | | ||
23 | SNDRV_PCM_INFO_INTERLEAVED | | ||
24 | SNDRV_PCM_INFO_PAUSE | | ||
25 | SNDRV_PCM_INFO_RESUME, | ||
26 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | ||
27 | SNDRV_PCM_FMTBIT_S24_LE | | ||
28 | SNDRV_PCM_FMTBIT_S32_LE, | ||
29 | .period_bytes_min = 32, | ||
30 | .period_bytes_max = 8192 - 32, | ||
31 | .periods_min = 1, | ||
32 | .periods_max = PAGE_SIZE/sizeof(pxa_dma_desc), | ||
33 | .buffer_bytes_max = 128 * 1024, | ||
34 | .fifo_size = 32, | ||
35 | }; | ||
36 | |||
37 | int __pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
38 | struct snd_pcm_hw_params *params) | ||
39 | { | ||
40 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
41 | struct pxa2xx_runtime_data *rtd = runtime->private_data; | ||
42 | size_t totsize = params_buffer_bytes(params); | ||
43 | size_t period = params_period_bytes(params); | ||
44 | pxa_dma_desc *dma_desc; | ||
45 | dma_addr_t dma_buff_phys, next_desc_phys; | ||
46 | |||
47 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
48 | runtime->dma_bytes = totsize; | ||
49 | |||
50 | dma_desc = rtd->dma_desc_array; | ||
51 | next_desc_phys = rtd->dma_desc_array_phys; | ||
52 | dma_buff_phys = runtime->dma_addr; | ||
53 | do { | ||
54 | next_desc_phys += sizeof(pxa_dma_desc); | ||
55 | dma_desc->ddadr = next_desc_phys; | ||
56 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
57 | dma_desc->dsadr = dma_buff_phys; | ||
58 | dma_desc->dtadr = rtd->params->dev_addr; | ||
59 | } else { | ||
60 | dma_desc->dsadr = rtd->params->dev_addr; | ||
61 | dma_desc->dtadr = dma_buff_phys; | ||
62 | } | ||
63 | if (period > totsize) | ||
64 | period = totsize; | ||
65 | dma_desc->dcmd = rtd->params->dcmd | period | DCMD_ENDIRQEN; | ||
66 | dma_desc++; | ||
67 | dma_buff_phys += period; | ||
68 | } while (totsize -= period); | ||
69 | dma_desc[-1].ddadr = rtd->dma_desc_array_phys; | ||
70 | |||
71 | return 0; | ||
72 | } | ||
73 | EXPORT_SYMBOL(__pxa2xx_pcm_hw_params); | ||
74 | |||
75 | int __pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) | ||
76 | { | ||
77 | struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; | ||
78 | |||
79 | if (rtd && rtd->params) | ||
80 | *rtd->params->drcmr = 0; | ||
81 | |||
82 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
83 | return 0; | ||
84 | } | ||
85 | EXPORT_SYMBOL(__pxa2xx_pcm_hw_free); | ||
86 | |||
87 | int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
88 | { | ||
89 | struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; | ||
90 | int ret = 0; | ||
91 | |||
92 | switch (cmd) { | ||
93 | case SNDRV_PCM_TRIGGER_START: | ||
94 | DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; | ||
95 | DCSR(prtd->dma_ch) = DCSR_RUN; | ||
96 | break; | ||
97 | |||
98 | case SNDRV_PCM_TRIGGER_STOP: | ||
99 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
100 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
101 | DCSR(prtd->dma_ch) &= ~DCSR_RUN; | ||
102 | break; | ||
103 | |||
104 | case SNDRV_PCM_TRIGGER_RESUME: | ||
105 | DCSR(prtd->dma_ch) |= DCSR_RUN; | ||
106 | break; | ||
107 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
108 | DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; | ||
109 | DCSR(prtd->dma_ch) |= DCSR_RUN; | ||
110 | break; | ||
111 | |||
112 | default: | ||
113 | ret = -EINVAL; | ||
114 | } | ||
115 | |||
116 | return ret; | ||
117 | } | ||
118 | EXPORT_SYMBOL(pxa2xx_pcm_trigger); | ||
119 | |||
120 | snd_pcm_uframes_t | ||
121 | pxa2xx_pcm_pointer(struct snd_pcm_substream *substream) | ||
122 | { | ||
123 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
124 | struct pxa2xx_runtime_data *prtd = runtime->private_data; | ||
125 | |||
126 | dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? | ||
127 | DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch); | ||
128 | snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr); | ||
129 | |||
130 | if (x == runtime->buffer_size) | ||
131 | x = 0; | ||
132 | return x; | ||
133 | } | ||
134 | EXPORT_SYMBOL(pxa2xx_pcm_pointer); | ||
135 | |||
136 | int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) | ||
137 | { | ||
138 | struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; | ||
139 | |||
140 | DCSR(prtd->dma_ch) &= ~DCSR_RUN; | ||
141 | DCSR(prtd->dma_ch) = 0; | ||
142 | DCMD(prtd->dma_ch) = 0; | ||
143 | *prtd->params->drcmr = prtd->dma_ch | DRCMR_MAPVLD; | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | EXPORT_SYMBOL(__pxa2xx_pcm_prepare); | ||
148 | |||
149 | void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id) | ||
150 | { | ||
151 | struct snd_pcm_substream *substream = dev_id; | ||
152 | struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; | ||
153 | int dcsr; | ||
154 | |||
155 | dcsr = DCSR(dma_ch); | ||
156 | DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN; | ||
157 | |||
158 | if (dcsr & DCSR_ENDINTR) { | ||
159 | snd_pcm_period_elapsed(substream); | ||
160 | } else { | ||
161 | printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n", | ||
162 | rtd->params->name, dma_ch, dcsr); | ||
163 | snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); | ||
164 | } | ||
165 | } | ||
166 | EXPORT_SYMBOL(pxa2xx_pcm_dma_irq); | ||
167 | |||
168 | int __pxa2xx_pcm_open(struct snd_pcm_substream *substream) | ||
169 | { | ||
170 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
171 | struct pxa2xx_runtime_data *rtd; | ||
172 | int ret; | ||
173 | |||
174 | runtime->hw = pxa2xx_pcm_hardware; | ||
175 | |||
176 | /* | ||
177 | * For mysterious reasons (and despite what the manual says) | ||
178 | * playback samples are lost if the DMA count is not a multiple | ||
179 | * of the DMA burst size. Let's add a rule to enforce that. | ||
180 | */ | ||
181 | ret = snd_pcm_hw_constraint_step(runtime, 0, | ||
182 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); | ||
183 | if (ret) | ||
184 | goto out; | ||
185 | |||
186 | ret = snd_pcm_hw_constraint_step(runtime, 0, | ||
187 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); | ||
188 | if (ret) | ||
189 | goto out; | ||
190 | |||
191 | ret = snd_pcm_hw_constraint_integer(runtime, | ||
192 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
193 | if (ret < 0) | ||
194 | goto out; | ||
195 | |||
196 | ret = -ENOMEM; | ||
197 | rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); | ||
198 | if (!rtd) | ||
199 | goto out; | ||
200 | rtd->dma_desc_array = | ||
201 | dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE, | ||
202 | &rtd->dma_desc_array_phys, GFP_KERNEL); | ||
203 | if (!rtd->dma_desc_array) | ||
204 | goto err1; | ||
205 | |||
206 | runtime->private_data = rtd; | ||
207 | return 0; | ||
208 | |||
209 | err1: | ||
210 | kfree(rtd); | ||
211 | out: | ||
212 | return ret; | ||
213 | } | ||
214 | EXPORT_SYMBOL(__pxa2xx_pcm_open); | ||
215 | |||
216 | int __pxa2xx_pcm_close(struct snd_pcm_substream *substream) | ||
217 | { | ||
218 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
219 | struct pxa2xx_runtime_data *rtd = runtime->private_data; | ||
220 | |||
221 | dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE, | ||
222 | rtd->dma_desc_array, rtd->dma_desc_array_phys); | ||
223 | kfree(rtd); | ||
224 | return 0; | ||
225 | } | ||
226 | EXPORT_SYMBOL(__pxa2xx_pcm_close); | ||
227 | |||
228 | int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream, | ||
229 | struct vm_area_struct *vma) | ||
230 | { | ||
231 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
232 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | ||
233 | runtime->dma_area, | ||
234 | runtime->dma_addr, | ||
235 | runtime->dma_bytes); | ||
236 | } | ||
237 | EXPORT_SYMBOL(pxa2xx_pcm_mmap); | ||
238 | |||
239 | int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
240 | { | ||
241 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
242 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
243 | size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; | ||
244 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
245 | buf->dev.dev = pcm->card->dev; | ||
246 | buf->private_data = NULL; | ||
247 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | ||
248 | &buf->addr, GFP_KERNEL); | ||
249 | if (!buf->area) | ||
250 | return -ENOMEM; | ||
251 | buf->bytes = size; | ||
252 | return 0; | ||
253 | } | ||
254 | EXPORT_SYMBOL(pxa2xx_pcm_preallocate_dma_buffer); | ||
255 | |||
256 | void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | ||
257 | { | ||
258 | struct snd_pcm_substream *substream; | ||
259 | struct snd_dma_buffer *buf; | ||
260 | int stream; | ||
261 | |||
262 | for (stream = 0; stream < 2; stream++) { | ||
263 | substream = pcm->streams[stream].substream; | ||
264 | if (!substream) | ||
265 | continue; | ||
266 | buf = &substream->dma_buffer; | ||
267 | if (!buf->area) | ||
268 | continue; | ||
269 | dma_free_writecombine(pcm->card->dev, buf->bytes, | ||
270 | buf->area, buf->addr); | ||
271 | buf->area = NULL; | ||
272 | } | ||
273 | } | ||
274 | EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers); | ||
275 | |||
276 | MODULE_AUTHOR("Nicolas Pitre"); | ||
277 | MODULE_DESCRIPTION("Intel PXA2xx sound library"); | ||
278 | MODULE_LICENSE("GPL"); | ||