diff options
Diffstat (limited to 'drivers/media/video/cx18/cx18-alsa-pcm.c')
-rw-r--r-- | drivers/media/video/cx18/cx18-alsa-pcm.c | 354 |
1 files changed, 354 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 000000000000..2bd312daeb1e --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-pcm.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* | ||
2 | * ALSA PCM device for the | ||
3 | * ALSA interface to cx18 PCM capture streams | ||
4 | * | ||
5 | * Copyright (C) 2009 Andy Walls <awalls@radix.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 | return snd_pcm_lib_ioctl(substream, cmd, arg); | ||
222 | } | ||
223 | |||
224 | |||
225 | static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, | ||
226 | size_t size) | ||
227 | { | ||
228 | struct snd_pcm_runtime *runtime = subs->runtime; | ||
229 | |||
230 | dprintk("Allocating vbuffer\n"); | ||
231 | if (runtime->dma_area) { | ||
232 | if (runtime->dma_bytes > size) | ||
233 | return 0; | ||
234 | |||
235 | vfree(runtime->dma_area); | ||
236 | } | ||
237 | runtime->dma_area = vmalloc(size); | ||
238 | if (!runtime->dma_area) | ||
239 | return -ENOMEM; | ||
240 | |||
241 | runtime->dma_bytes = size; | ||
242 | |||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream, | ||
247 | struct snd_pcm_hw_params *params) | ||
248 | { | ||
249 | int ret; | ||
250 | |||
251 | dprintk("%s called\n", __func__); | ||
252 | |||
253 | ret = snd_pcm_alloc_vmalloc_buffer(substream, | ||
254 | params_buffer_bytes(params)); | ||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) | ||
259 | { | ||
260 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
261 | unsigned long flags; | ||
262 | |||
263 | spin_lock_irqsave(&cxsc->slock, flags); | ||
264 | if (substream->runtime->dma_area) { | ||
265 | dprintk("freeing pcm capture region\n"); | ||
266 | vfree(substream->runtime->dma_area); | ||
267 | substream->runtime->dma_area = NULL; | ||
268 | } | ||
269 | spin_unlock_irqrestore(&cxsc->slock, flags); | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) | ||
275 | { | ||
276 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
277 | |||
278 | cxsc->hwptr_done_capture = 0; | ||
279 | cxsc->capture_transfer_done = 0; | ||
280 | |||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
285 | { | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static | ||
290 | snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) | ||
291 | { | ||
292 | unsigned long flags; | ||
293 | snd_pcm_uframes_t hwptr_done; | ||
294 | struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); | ||
295 | |||
296 | spin_lock_irqsave(&cxsc->slock, flags); | ||
297 | hwptr_done = cxsc->hwptr_done_capture; | ||
298 | spin_unlock_irqrestore(&cxsc->slock, flags); | ||
299 | |||
300 | return hwptr_done; | ||
301 | } | ||
302 | |||
303 | static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, | ||
304 | unsigned long offset) | ||
305 | { | ||
306 | void *pageptr = subs->runtime->dma_area + offset; | ||
307 | |||
308 | return vmalloc_to_page(pageptr); | ||
309 | } | ||
310 | |||
311 | static struct snd_pcm_ops snd_cx18_pcm_capture_ops = { | ||
312 | .open = snd_cx18_pcm_capture_open, | ||
313 | .close = snd_cx18_pcm_capture_close, | ||
314 | .ioctl = snd_cx18_pcm_ioctl, | ||
315 | .hw_params = snd_cx18_pcm_hw_params, | ||
316 | .hw_free = snd_cx18_pcm_hw_free, | ||
317 | .prepare = snd_cx18_pcm_prepare, | ||
318 | .trigger = snd_cx18_pcm_trigger, | ||
319 | .pointer = snd_cx18_pcm_pointer, | ||
320 | .page = snd_pcm_get_vmalloc_page, | ||
321 | }; | ||
322 | |||
323 | int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) | ||
324 | { | ||
325 | struct snd_pcm *sp; | ||
326 | struct snd_card *sc = cxsc->sc; | ||
327 | struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; | ||
328 | struct cx18 *cx = to_cx18(v4l2_dev); | ||
329 | int ret; | ||
330 | |||
331 | ret = snd_pcm_new(sc, "CX23418 PCM", | ||
332 | 0, /* PCM device 0, the only one for this card */ | ||
333 | 0, /* 0 playback substreams */ | ||
334 | 1, /* 1 capture substream */ | ||
335 | &sp); | ||
336 | if (ret) { | ||
337 | CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", | ||
338 | __func__, ret); | ||
339 | goto err_exit; | ||
340 | } | ||
341 | |||
342 | spin_lock_init(&cxsc->slock); | ||
343 | |||
344 | snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, | ||
345 | &snd_cx18_pcm_capture_ops); | ||
346 | sp->info_flags = 0; | ||
347 | sp->private_data = cxsc; | ||
348 | strlcpy(sp->name, cx->card_name, sizeof(sp->name)); | ||
349 | |||
350 | return 0; | ||
351 | |||
352 | err_exit: | ||
353 | return ret; | ||
354 | } | ||