diff options
Diffstat (limited to 'drivers/media/video/cx18/cx18-alsa-pcm.c')
-rw-r--r-- | drivers/media/video/cx18/cx18-alsa-pcm.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/drivers/media/video/cx18/cx18-alsa-pcm.c b/drivers/media/video/cx18/cx18-alsa-pcm.c new file mode 100644 index 00000000000..82d195be919 --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-pcm.c | |||
@@ -0,0 +1,360 @@ | |||
1 | /* | ||
2 | * ALSA PCM device for the | ||
3 | * ALSA interface to cx18 PCM capture streams | ||
4 | * | ||
5 | * Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net> | ||
6 | * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> | ||
7 | * | ||
8 | * Portions of this work were sponsored by ONELAN Limited. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | ||
23 | * 02111-1307 USA | ||
24 | */ | ||
25 | |||
26 | #include <linux/init.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/vmalloc.h> | ||
29 | |||
30 | #include <media/v4l2-device.h> | ||
31 | |||
32 | #include <sound/core.h> | ||
33 | #include <sound/pcm.h> | ||
34 | |||
35 | #include "cx18-driver.h" | ||
36 | #include "cx18-queue.h" | ||
37 | #include "cx18-streams.h" | ||
38 | #include "cx18-fileops.h" | ||
39 | #include "cx18-alsa.h" | ||
40 | |||
41 | static unsigned int pcm_debug; | ||
42 | module_param(pcm_debug, int, 0644); | ||
43 | MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); | ||
44 | |||
45 | #define dprintk(fmt, arg...) do { \ | ||
46 | if (pcm_debug) \ | ||
47 | printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ | ||
48 | __func__, ##arg); \ | ||
49 | } while (0) | ||
50 | |||
51 | static struct snd_pcm_hardware snd_cx18_hw_capture = { | ||
52 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
53 | SNDRV_PCM_INFO_MMAP | | ||
54 | SNDRV_PCM_INFO_INTERLEAVED | | ||
55 | SNDRV_PCM_INFO_MMAP_VALID, | ||
56 | |||
57 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
58 | |||
59 | .rates = SNDRV_PCM_RATE_48000, | ||
60 | |||
61 | .rate_min = 48000, | ||
62 | .rate_max = 48000, | ||
63 | .channels_min = 2, | ||
64 | .channels_max = 2, | ||
65 | .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ | ||
66 | .period_bytes_min = 64, /* 12544/2, */ | ||
67 | .period_bytes_max = 12544, | ||
68 | .periods_min = 2, | ||
69 | .periods_max = 98, /* 12544, */ | ||
70 | }; | ||
71 | |||
72 | void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data, | ||
73 | size_t num_bytes) | ||
74 | { | ||
75 | struct snd_pcm_substream *substream; | ||
76 | struct snd_pcm_runtime *runtime; | ||
77 | unsigned int oldptr; | ||
78 | unsigned int stride; | ||
79 | int period_elapsed = 0; | ||
80 | int length; | ||
81 | |||
82 | dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zd\n", cxsc, | ||
83 | pcm_data, num_bytes); | ||
84 | |||
85 | substream = cxsc->capture_pcm_substream; | ||
86 | if (substream == NULL) { | ||
87 | dprintk("substream was NULL\n"); | ||
88 | return; | ||
89 | } | ||
90 | |||
91 | runtime = substream->runtime; | ||
92 | if (runtime == NULL) { | ||
93 | dprintk("runtime was NULL\n"); | ||
94 | return; | ||
95 | } | ||
96 | |||
97 | stride = runtime->frame_bits >> 3; | ||
98 | if (stride == 0) { | ||
99 | dprintk("stride is zero\n"); | ||
100 | return; | ||
101 | } | ||
102 | |||
103 | length = num_bytes / stride; | ||
104 | if (length == 0) { | ||
105 | dprintk("%s: length was zero\n", __func__); | ||
106 | return; | ||
107 | } | ||
108 | |||
109 | if (runtime->dma_area == NULL) { | ||
110 | dprintk("dma area was NULL - ignoring\n"); | ||
111 | return; | ||
112 | } | ||
113 | |||
114 | oldptr = cxsc->hwptr_done_capture; | ||
115 | if (oldptr + length >= runtime->buffer_size) { | ||
116 | unsigned int cnt = | ||
117 | runtime->buffer_size - oldptr; | ||
118 | memcpy(runtime->dma_area + oldptr * stride, pcm_data, | ||
119 | cnt * stride); | ||
120 | memcpy(runtime->dma_area, pcm_data + cnt * stride, | ||
121 | length * stride - cnt * stride); | ||
122 | } else { | ||
123 | memcpy(runtime->dma_area + oldptr * stride, pcm_data, | ||
124 | length * stride); | ||
125 | } | ||
126 | snd_pcm_stream_lock(substream); | ||
127 | |||
128 | cxsc->hwptr_done_capture += length; | ||
129 | if (cxsc->hwptr_done_capture >= | ||
130 | runtime->buffer_size) | ||
131 | cxsc->hwptr_done_capture -= | ||
132 | runtime->buffer_size; | ||
133 | |||
134 | cxsc->capture_transfer_done += length; | ||
135 | if (cxsc->capture_transfer_done >= | ||
136 | runtime->period_size) { | ||
137 | cxsc->capture_transfer_done -= | ||
138 | runtime->period_size; | ||
139 | period_elapsed = 1; | ||
140 | } | ||
141 | |||
142 | snd_pcm_stream_unlock(substream); | ||
143 | |||
144 | if (period_elapsed) | ||
145 | snd_pcm_period_elapsed(substream); | ||
146 | } | ||
147 | |||
148 | static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream) | ||
149 | { | ||
150 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
151 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
152 | struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; | ||
153 | struct cx18 *cx = to_cx18(v4l2_dev); | ||
154 | struct cx18_stream *s; | ||
155 | struct cx18_open_id item; | ||
156 | int ret; | ||
157 | |||
158 | /* Instruct the cx18 to start sending packets */ | ||
159 | snd_cx18_lock(cxsc); | ||
160 | s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; | ||
161 | |||
162 | item.cx = cx; | ||
163 | item.type = s->type; | ||
164 | item.open_id = cx->open_id++; | ||
165 | |||
166 | /* See if the stream is available */ | ||
167 | if (cx18_claim_stream(&item, item.type)) { | ||
168 | /* No, it's already in use */ | ||
169 | snd_cx18_unlock(cxsc); | ||
170 | return -EBUSY; | ||
171 | } | ||
172 | |||
173 | if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || | ||
174 | test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { | ||
175 | /* We're already streaming. No additional action required */ | ||
176 | snd_cx18_unlock(cxsc); | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | |||
181 | runtime->hw = snd_cx18_hw_capture; | ||
182 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); | ||
183 | cxsc->capture_pcm_substream = substream; | ||
184 | runtime->private_data = cx; | ||
185 | |||
186 | cx->pcm_announce_callback = cx18_alsa_announce_pcm_data; | ||
187 | |||
188 | /* Not currently streaming, so start it up */ | ||
189 | set_bit(CX18_F_S_STREAMING, &s->s_flags); | ||
190 | ret = cx18_start_v4l2_encode_stream(s); | ||
191 | snd_cx18_unlock(cxsc); | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) | ||
197 | { | ||
198 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
199 | struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; | ||
200 | struct cx18 *cx = to_cx18(v4l2_dev); | ||
201 | struct cx18_stream *s; | ||
202 | int ret; | ||
203 | |||
204 | /* Instruct the cx18 to stop sending packets */ | ||
205 | snd_cx18_lock(cxsc); | ||
206 | s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; | ||
207 | ret = cx18_stop_v4l2_encode_stream(s, 0); | ||
208 | clear_bit(CX18_F_S_STREAMING, &s->s_flags); | ||
209 | |||
210 | cx18_release_stream(s); | ||
211 | |||
212 | cx->pcm_announce_callback = NULL; | ||
213 | snd_cx18_unlock(cxsc); | ||
214 | |||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream, | ||
219 | unsigned int cmd, void *arg) | ||
220 | { | ||
221 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
222 | int ret; | ||
223 | |||
224 | snd_cx18_lock(cxsc); | ||
225 | ret = snd_pcm_lib_ioctl(substream, cmd, arg); | ||
226 | snd_cx18_unlock(cxsc); | ||
227 | return ret; | ||
228 | } | ||
229 | |||
230 | |||
231 | static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, | ||
232 | size_t size) | ||
233 | { | ||
234 | struct snd_pcm_runtime *runtime = subs->runtime; | ||
235 | |||
236 | dprintk("Allocating vbuffer\n"); | ||
237 | if (runtime->dma_area) { | ||
238 | if (runtime->dma_bytes > size) | ||
239 | return 0; | ||
240 | |||
241 | vfree(runtime->dma_area); | ||
242 | } | ||
243 | runtime->dma_area = vmalloc(size); | ||
244 | if (!runtime->dma_area) | ||
245 | return -ENOMEM; | ||
246 | |||
247 | runtime->dma_bytes = size; | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream, | ||
253 | struct snd_pcm_hw_params *params) | ||
254 | { | ||
255 | int ret; | ||
256 | |||
257 | dprintk("%s called\n", __func__); | ||
258 | |||
259 | ret = snd_pcm_alloc_vmalloc_buffer(substream, | ||
260 | params_buffer_bytes(params)); | ||
261 | return 0; | ||
262 | } | ||
263 | |||
264 | static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) | ||
265 | { | ||
266 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
267 | unsigned long flags; | ||
268 | |||
269 | spin_lock_irqsave(&cxsc->slock, flags); | ||
270 | if (substream->runtime->dma_area) { | ||
271 | dprintk("freeing pcm capture region\n"); | ||
272 | vfree(substream->runtime->dma_area); | ||
273 | substream->runtime->dma_area = NULL; | ||
274 | } | ||
275 | spin_unlock_irqrestore(&cxsc->slock, flags); | ||
276 | |||
277 | return 0; | ||
278 | } | ||
279 | |||
280 | static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) | ||
281 | { | ||
282 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
283 | |||
284 | cxsc->hwptr_done_capture = 0; | ||
285 | cxsc->capture_transfer_done = 0; | ||
286 | |||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
291 | { | ||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static | ||
296 | snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) | ||
297 | { | ||
298 | unsigned long flags; | ||
299 | snd_pcm_uframes_t hwptr_done; | ||
300 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
301 | |||
302 | spin_lock_irqsave(&cxsc->slock, flags); | ||
303 | hwptr_done = cxsc->hwptr_done_capture; | ||
304 | spin_unlock_irqrestore(&cxsc->slock, flags); | ||
305 | |||
306 | return hwptr_done; | ||
307 | } | ||
308 | |||
309 | static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, | ||
310 | unsigned long offset) | ||
311 | { | ||
312 | void *pageptr = subs->runtime->dma_area + offset; | ||
313 | |||
314 | return vmalloc_to_page(pageptr); | ||
315 | } | ||
316 | |||
317 | static struct snd_pcm_ops snd_cx18_pcm_capture_ops = { | ||
318 | .open = snd_cx18_pcm_capture_open, | ||
319 | .close = snd_cx18_pcm_capture_close, | ||
320 | .ioctl = snd_cx18_pcm_ioctl, | ||
321 | .hw_params = snd_cx18_pcm_hw_params, | ||
322 | .hw_free = snd_cx18_pcm_hw_free, | ||
323 | .prepare = snd_cx18_pcm_prepare, | ||
324 | .trigger = snd_cx18_pcm_trigger, | ||
325 | .pointer = snd_cx18_pcm_pointer, | ||
326 | .page = snd_pcm_get_vmalloc_page, | ||
327 | }; | ||
328 | |||
329 | int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) | ||
330 | { | ||
331 | struct snd_pcm *sp; | ||
332 | struct snd_card *sc = cxsc->sc; | ||
333 | struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; | ||
334 | struct cx18 *cx = to_cx18(v4l2_dev); | ||
335 | int ret; | ||
336 | |||
337 | ret = snd_pcm_new(sc, "CX23418 PCM", | ||
338 | 0, /* PCM device 0, the only one for this card */ | ||
339 | 0, /* 0 playback substreams */ | ||
340 | 1, /* 1 capture substream */ | ||
341 | &sp); | ||
342 | if (ret) { | ||
343 | CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", | ||
344 | __func__, ret); | ||
345 | goto err_exit; | ||
346 | } | ||
347 | |||
348 | spin_lock_init(&cxsc->slock); | ||
349 | |||
350 | snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, | ||
351 | &snd_cx18_pcm_capture_ops); | ||
352 | sp->info_flags = 0; | ||
353 | sp->private_data = cxsc; | ||
354 | strlcpy(sp->name, cx->card_name, sizeof(sp->name)); | ||
355 | |||
356 | return 0; | ||
357 | |||
358 | err_exit: | ||
359 | return ret; | ||
360 | } | ||