]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | #include <media/saa7146_vv.h> |
2 | #include <linux/version.h> | |
3 | ||
4 | #define BOARD_CAN_DO_VBI(dev) (dev->revision != 0 && dev->vv_data->vbi_minor != -1) | |
5 | ||
6 | /****************************************************************************/ | |
7 | /* resource management functions, shamelessly stolen from saa7134 driver */ | |
8 | ||
9 | int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) | |
10 | { | |
11 | struct saa7146_dev *dev = fh->dev; | |
12 | struct saa7146_vv *vv = dev->vv_data; | |
13 | ||
14 | if (fh->resources & bit) { | |
15 | DEB_D(("already allocated! want: 0x%02x, cur:0x%02x\n",bit,vv->resources)); | |
16 | /* have it already allocated */ | |
17 | return 1; | |
18 | } | |
19 | ||
20 | /* is it free? */ | |
21 | down(&dev->lock); | |
22 | if (vv->resources & bit) { | |
23 | DEB_D(("locked! vv->resources:0x%02x, we want:0x%02x\n",vv->resources,bit)); | |
24 | /* no, someone else uses it */ | |
25 | up(&dev->lock); | |
26 | return 0; | |
27 | } | |
28 | /* it's free, grab it */ | |
29 | fh->resources |= bit; | |
30 | vv->resources |= bit; | |
31 | DEB_D(("res: get 0x%02x, cur:0x%02x\n",bit,vv->resources)); | |
32 | up(&dev->lock); | |
33 | return 1; | |
34 | } | |
35 | ||
36 | void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) | |
37 | { | |
38 | struct saa7146_dev *dev = fh->dev; | |
39 | struct saa7146_vv *vv = dev->vv_data; | |
40 | ||
41 | if ((fh->resources & bits) != bits) | |
42 | BUG(); | |
43 | ||
44 | down(&dev->lock); | |
45 | fh->resources &= ~bits; | |
46 | vv->resources &= ~bits; | |
47 | DEB_D(("res: put 0x%02x, cur:0x%02x\n",bits,vv->resources)); | |
48 | up(&dev->lock); | |
49 | } | |
50 | ||
51 | ||
52 | /********************************************************************************/ | |
53 | /* common dma functions */ | |
54 | ||
55 | void saa7146_dma_free(struct saa7146_dev *dev,struct saa7146_buf *buf) | |
56 | { | |
57 | DEB_EE(("dev:%p, buf:%p\n",dev,buf)); | |
58 | ||
59 | if (in_interrupt()) | |
60 | BUG(); | |
61 | ||
62 | videobuf_waiton(&buf->vb,0,0); | |
63 | videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma); | |
64 | videobuf_dma_free(&buf->vb.dma); | |
65 | buf->vb.state = STATE_NEEDS_INIT; | |
66 | } | |
67 | ||
68 | ||
69 | /********************************************************************************/ | |
70 | /* common buffer functions */ | |
71 | ||
72 | int saa7146_buffer_queue(struct saa7146_dev *dev, | |
73 | struct saa7146_dmaqueue *q, | |
74 | struct saa7146_buf *buf) | |
75 | { | |
76 | assert_spin_locked(&dev->slock); | |
77 | DEB_EE(("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf)); | |
78 | ||
79 | BUG_ON(!q); | |
80 | ||
81 | if (NULL == q->curr) { | |
82 | q->curr = buf; | |
83 | DEB_D(("immediately activating buffer %p\n", buf)); | |
84 | buf->activate(dev,buf,NULL); | |
85 | } else { | |
86 | list_add_tail(&buf->vb.queue,&q->queue); | |
87 | buf->vb.state = STATE_QUEUED; | |
88 | DEB_D(("adding buffer %p to queue. (active buffer present)\n", buf)); | |
89 | } | |
90 | return 0; | |
91 | } | |
92 | ||
93 | void saa7146_buffer_finish(struct saa7146_dev *dev, | |
94 | struct saa7146_dmaqueue *q, | |
95 | int state) | |
96 | { | |
97 | assert_spin_locked(&dev->slock); | |
98 | DEB_EE(("dev:%p, dmaq:%p, state:%d\n", dev, q, state)); | |
99 | DEB_EE(("q->curr:%p\n",q->curr)); | |
100 | ||
101 | BUG_ON(!q->curr); | |
102 | ||
103 | /* finish current buffer */ | |
104 | if (NULL == q->curr) { | |
105 | DEB_D(("aiii. no current buffer\n")); | |
106 | return; | |
107 | } | |
108 | ||
109 | q->curr->vb.state = state; | |
110 | do_gettimeofday(&q->curr->vb.ts); | |
111 | wake_up(&q->curr->vb.done); | |
112 | ||
113 | q->curr = NULL; | |
114 | } | |
115 | ||
116 | void saa7146_buffer_next(struct saa7146_dev *dev, | |
117 | struct saa7146_dmaqueue *q, int vbi) | |
118 | { | |
119 | struct saa7146_buf *buf,*next = NULL; | |
120 | ||
121 | BUG_ON(!q); | |
122 | ||
123 | DEB_INT(("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi)); | |
124 | ||
125 | assert_spin_locked(&dev->slock); | |
126 | if (!list_empty(&q->queue)) { | |
127 | /* activate next one from queue */ | |
128 | buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); | |
129 | list_del(&buf->vb.queue); | |
130 | if (!list_empty(&q->queue)) | |
131 | next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); | |
132 | q->curr = buf; | |
133 | DEB_INT(("next buffer: buf:%p, prev:%p, next:%p\n", buf, q->queue.prev,q->queue.next)); | |
134 | buf->activate(dev,buf,next); | |
135 | } else { | |
136 | DEB_INT(("no next buffer. stopping.\n")); | |
137 | if( 0 != vbi ) { | |
138 | /* turn off video-dma3 */ | |
139 | saa7146_write(dev,MC1, MASK_20); | |
140 | } else { | |
141 | /* nothing to do -- just prevent next video-dma1 transfer | |
142 | by lowering the protection address */ | |
143 | ||
144 | // fixme: fix this for vflip != 0 | |
145 | ||
146 | saa7146_write(dev, PROT_ADDR1, 0); | |
147 | saa7146_write(dev, MC2, (MASK_02|MASK_18)); | |
148 | ||
149 | /* write the address of the rps-program */ | |
150 | saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); | |
151 | /* turn on rps */ | |
152 | saa7146_write(dev, MC1, (MASK_12 | MASK_28)); | |
153 | ||
154 | /* | |
155 | printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); | |
156 | printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); | |
157 | printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); | |
158 | printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); | |
159 | printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); | |
160 | printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); | |
161 | */ | |
162 | } | |
163 | del_timer(&q->timeout); | |
164 | } | |
165 | } | |
166 | ||
167 | void saa7146_buffer_timeout(unsigned long data) | |
168 | { | |
169 | struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; | |
170 | struct saa7146_dev *dev = q->dev; | |
171 | unsigned long flags; | |
172 | ||
173 | DEB_EE(("dev:%p, dmaq:%p\n", dev, q)); | |
174 | ||
175 | spin_lock_irqsave(&dev->slock,flags); | |
176 | if (q->curr) { | |
177 | DEB_D(("timeout on %p\n", q->curr)); | |
178 | saa7146_buffer_finish(dev,q,STATE_ERROR); | |
179 | } | |
180 | ||
181 | /* we don't restart the transfer here like other drivers do. when | |
182 | a streaming capture is disabled, the timeout function will be | |
183 | called for the current buffer. if we activate the next buffer now, | |
184 | we mess up our capture logic. if a timeout occurs on another buffer, | |
185 | then something is seriously broken before, so no need to buffer the | |
186 | next capture IMHO... */ | |
187 | /* | |
188 | saa7146_buffer_next(dev,q); | |
189 | */ | |
190 | spin_unlock_irqrestore(&dev->slock,flags); | |
191 | } | |
192 | ||
193 | /********************************************************************************/ | |
194 | /* file operations */ | |
195 | ||
196 | static int fops_open(struct inode *inode, struct file *file) | |
197 | { | |
198 | unsigned int minor = iminor(inode); | |
199 | struct saa7146_dev *h = NULL, *dev = NULL; | |
200 | struct list_head *list; | |
201 | struct saa7146_fh *fh = NULL; | |
202 | int result = 0; | |
203 | ||
204 | enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
205 | ||
206 | DEB_EE(("inode:%p, file:%p, minor:%d\n",inode,file,minor)); | |
207 | ||
208 | if (down_interruptible(&saa7146_devices_lock)) | |
209 | return -ERESTARTSYS; | |
210 | ||
211 | list_for_each(list,&saa7146_devices) { | |
212 | h = list_entry(list, struct saa7146_dev, item); | |
213 | if( NULL == h->vv_data ) { | |
214 | DEB_D(("device %p has not registered video devices.\n",h)); | |
215 | continue; | |
216 | } | |
217 | DEB_D(("trying: %p @ major %d,%d\n",h,h->vv_data->video_minor,h->vv_data->vbi_minor)); | |
218 | ||
219 | if (h->vv_data->video_minor == minor) { | |
220 | dev = h; | |
221 | } | |
222 | if (h->vv_data->vbi_minor == minor) { | |
223 | type = V4L2_BUF_TYPE_VBI_CAPTURE; | |
224 | dev = h; | |
225 | } | |
226 | } | |
227 | if (NULL == dev) { | |
228 | DEB_S(("no such video device.\n")); | |
229 | result = -ENODEV; | |
230 | goto out; | |
231 | } | |
232 | ||
233 | DEB_D(("using: %p\n",dev)); | |
234 | ||
235 | /* check if an extension is registered */ | |
236 | if( NULL == dev->ext ) { | |
237 | DEB_S(("no extension registered for this device.\n")); | |
238 | result = -ENODEV; | |
239 | goto out; | |
240 | } | |
241 | ||
242 | /* allocate per open data */ | |
243 | fh = kmalloc(sizeof(*fh),GFP_KERNEL); | |
244 | if (NULL == fh) { | |
245 | DEB_S(("cannot allocate memory for per open data.\n")); | |
246 | result = -ENOMEM; | |
247 | goto out; | |
248 | } | |
249 | memset(fh,0,sizeof(*fh)); | |
250 | ||
251 | file->private_data = fh; | |
252 | fh->dev = dev; | |
253 | fh->type = type; | |
254 | ||
255 | if( fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) { | |
256 | DEB_S(("initializing vbi...\n")); | |
257 | result = saa7146_vbi_uops.open(dev,file); | |
258 | } else { | |
259 | DEB_S(("initializing video...\n")); | |
260 | result = saa7146_video_uops.open(dev,file); | |
261 | } | |
262 | ||
263 | if (0 != result) { | |
264 | goto out; | |
265 | } | |
266 | ||
267 | if( 0 == try_module_get(dev->ext->module)) { | |
268 | result = -EINVAL; | |
269 | goto out; | |
270 | } | |
271 | ||
272 | result = 0; | |
273 | out: | |
274 | if( fh != 0 && result != 0 ) { | |
275 | kfree(fh); | |
276 | file->private_data = NULL; | |
277 | } | |
278 | up(&saa7146_devices_lock); | |
279 | return result; | |
280 | } | |
281 | ||
282 | static int fops_release(struct inode *inode, struct file *file) | |
283 | { | |
284 | struct saa7146_fh *fh = file->private_data; | |
285 | struct saa7146_dev *dev = fh->dev; | |
286 | ||
287 | DEB_EE(("inode:%p, file:%p\n",inode,file)); | |
288 | ||
289 | if (down_interruptible(&saa7146_devices_lock)) | |
290 | return -ERESTARTSYS; | |
291 | ||
292 | if( fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) { | |
293 | saa7146_vbi_uops.release(dev,file); | |
294 | } else { | |
295 | saa7146_video_uops.release(dev,file); | |
296 | } | |
297 | ||
298 | module_put(dev->ext->module); | |
299 | file->private_data = NULL; | |
300 | kfree(fh); | |
301 | ||
302 | up(&saa7146_devices_lock); | |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
307 | int saa7146_video_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg); | |
308 | static int fops_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | |
309 | { | |
310 | /* | |
311 | DEB_EE(("inode:%p, file:%p, cmd:%d, arg:%li\n",inode, file, cmd, arg)); | |
312 | */ | |
313 | return video_usercopy(inode, file, cmd, arg, saa7146_video_do_ioctl); | |
314 | } | |
315 | ||
316 | static int fops_mmap(struct file *file, struct vm_area_struct * vma) | |
317 | { | |
318 | struct saa7146_fh *fh = file->private_data; | |
319 | struct videobuf_queue *q; | |
320 | ||
321 | switch (fh->type) { | |
322 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: { | |
323 | DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n",file, vma)); | |
324 | q = &fh->video_q; | |
325 | break; | |
326 | } | |
327 | case V4L2_BUF_TYPE_VBI_CAPTURE: { | |
328 | DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n",file, vma)); | |
329 | q = &fh->vbi_q; | |
330 | break; | |
331 | } | |
332 | default: | |
333 | BUG(); | |
334 | return 0; | |
335 | } | |
336 | return videobuf_mmap_mapper(q,vma); | |
337 | } | |
338 | ||
339 | static unsigned int fops_poll(struct file *file, struct poll_table_struct *wait) | |
340 | { | |
341 | struct saa7146_fh *fh = file->private_data; | |
342 | struct videobuf_buffer *buf = NULL; | |
343 | struct videobuf_queue *q; | |
344 | ||
345 | DEB_EE(("file:%p, poll:%p\n",file, wait)); | |
346 | ||
347 | if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { | |
348 | if( 0 == fh->vbi_q.streaming ) | |
349 | return videobuf_poll_stream(file, &fh->vbi_q, wait); | |
350 | q = &fh->vbi_q; | |
351 | } else { | |
352 | DEB_D(("using video queue.\n")); | |
353 | q = &fh->video_q; | |
354 | } | |
355 | ||
356 | if (!list_empty(&q->stream)) | |
357 | buf = list_entry(q->stream.next, struct videobuf_buffer, stream); | |
358 | ||
359 | if (!buf) { | |
360 | DEB_D(("buf == NULL!\n")); | |
361 | return POLLERR; | |
362 | } | |
363 | ||
364 | poll_wait(file, &buf->done, wait); | |
365 | if (buf->state == STATE_DONE || buf->state == STATE_ERROR) { | |
366 | DEB_D(("poll succeeded!\n")); | |
367 | return POLLIN|POLLRDNORM; | |
368 | } | |
369 | ||
370 | DEB_D(("nothing to poll for, buf->state:%d\n",buf->state)); | |
371 | return 0; | |
372 | } | |
373 | ||
374 | static ssize_t fops_read(struct file *file, char __user *data, size_t count, loff_t *ppos) | |
375 | { | |
376 | struct saa7146_fh *fh = file->private_data; | |
377 | ||
378 | switch (fh->type) { | |
379 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: { | |
380 | // DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%lun", file, data, (unsigned long)count)); | |
381 | return saa7146_video_uops.read(file,data,count,ppos); | |
382 | } | |
383 | case V4L2_BUF_TYPE_VBI_CAPTURE: { | |
384 | // DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%lu\n", file, data, (unsigned long)count)); | |
385 | return saa7146_vbi_uops.read(file,data,count,ppos); | |
386 | } | |
387 | break; | |
388 | default: | |
389 | BUG(); | |
390 | return 0; | |
391 | } | |
392 | } | |
393 | ||
394 | static struct file_operations video_fops = | |
395 | { | |
396 | .owner = THIS_MODULE, | |
397 | .open = fops_open, | |
398 | .release = fops_release, | |
399 | .read = fops_read, | |
400 | .poll = fops_poll, | |
401 | .mmap = fops_mmap, | |
402 | .ioctl = fops_ioctl, | |
403 | .llseek = no_llseek, | |
404 | }; | |
405 | ||
52c1da39 | 406 | static void vv_callback(struct saa7146_dev *dev, unsigned long status) |
1da177e4 LT |
407 | { |
408 | u32 isr = status; | |
409 | ||
410 | DEB_INT(("dev:%p, isr:0x%08x\n",dev,(u32)status)); | |
411 | ||
412 | if (0 != (isr & (MASK_27))) { | |
413 | DEB_INT(("irq: RPS0 (0x%08x).\n",isr)); | |
414 | saa7146_video_uops.irq_done(dev,isr); | |
415 | } | |
416 | ||
417 | if (0 != (isr & (MASK_28))) { | |
418 | u32 mc2 = saa7146_read(dev, MC2); | |
419 | if( 0 != (mc2 & MASK_15)) { | |
420 | DEB_INT(("irq: RPS1 vbi workaround (0x%08x).\n",isr)); | |
421 | wake_up(&dev->vv_data->vbi_wq); | |
422 | saa7146_write(dev,MC2, MASK_31); | |
423 | return; | |
424 | } | |
425 | DEB_INT(("irq: RPS1 (0x%08x).\n",isr)); | |
426 | saa7146_vbi_uops.irq_done(dev,isr); | |
427 | } | |
428 | } | |
429 | ||
430 | static struct video_device device_template = | |
431 | { | |
432 | .hardware = VID_HARDWARE_SAA7146, | |
433 | .fops = &video_fops, | |
434 | .minor = -1, | |
435 | }; | |
436 | ||
437 | int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) | |
438 | { | |
439 | struct saa7146_vv *vv = kmalloc (sizeof(struct saa7146_vv),GFP_KERNEL); | |
440 | if( NULL == vv ) { | |
441 | ERR(("out of memory. aborting.\n")); | |
442 | return -1; | |
443 | } | |
444 | memset(vv, 0x0, sizeof(*vv)); | |
445 | ||
446 | DEB_EE(("dev:%p\n",dev)); | |
447 | ||
448 | /* set default values for video parts of the saa7146 */ | |
449 | saa7146_write(dev, BCS_CTRL, 0x80400040); | |
450 | ||
451 | /* enable video-port pins */ | |
452 | saa7146_write(dev, MC1, (MASK_10 | MASK_26)); | |
453 | ||
454 | /* save per-device extension data (one extension can | |
455 | handle different devices that might need different | |
456 | configuration data) */ | |
457 | dev->ext_vv_data = ext_vv; | |
458 | ||
459 | vv->video_minor = -1; | |
460 | vv->vbi_minor = -1; | |
461 | ||
462 | vv->d_clipping.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_CLIPPING_MEM, &vv->d_clipping.dma_handle); | |
463 | if( NULL == vv->d_clipping.cpu_addr ) { | |
464 | ERR(("out of memory. aborting.\n")); | |
465 | kfree(vv); | |
466 | return -1; | |
467 | } | |
468 | memset(vv->d_clipping.cpu_addr, 0x0, SAA7146_CLIPPING_MEM); | |
469 | ||
470 | saa7146_video_uops.init(dev,vv); | |
471 | saa7146_vbi_uops.init(dev,vv); | |
472 | ||
473 | dev->vv_data = vv; | |
474 | dev->vv_callback = &vv_callback; | |
475 | ||
476 | return 0; | |
477 | } | |
478 | ||
479 | int saa7146_vv_release(struct saa7146_dev* dev) | |
480 | { | |
481 | struct saa7146_vv *vv = dev->vv_data; | |
482 | ||
483 | DEB_EE(("dev:%p\n",dev)); | |
484 | ||
485 | pci_free_consistent(dev->pci, SAA7146_RPS_MEM, vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); | |
486 | kfree(vv); | |
487 | dev->vv_data = NULL; | |
488 | dev->vv_callback = NULL; | |
489 | ||
490 | return 0; | |
491 | } | |
492 | ||
493 | int saa7146_register_device(struct video_device **vid, struct saa7146_dev* dev, | |
494 | char *name, int type) | |
495 | { | |
496 | struct saa7146_vv *vv = dev->vv_data; | |
497 | struct video_device *vfd; | |
498 | ||
499 | DEB_EE(("dev:%p, name:'%s', type:%d\n",dev,name,type)); | |
500 | ||
501 | // released by vfd->release | |
502 | vfd = video_device_alloc(); | |
503 | if (vfd == NULL) | |
504 | return -ENOMEM; | |
505 | ||
506 | memcpy(vfd, &device_template, sizeof(struct video_device)); | |
507 | strlcpy(vfd->name, name, sizeof(vfd->name)); | |
508 | vfd->release = video_device_release; | |
509 | vfd->priv = dev; | |
510 | ||
511 | // fixme: -1 should be an insmod parameter *for the extension* (like "video_nr"); | |
512 | if (video_register_device(vfd, type, -1) < 0) { | |
513 | ERR(("cannot register v4l2 device. skipping.\n")); | |
514 | return -1; | |
515 | } | |
516 | ||
517 | if( VFL_TYPE_GRABBER == type ) { | |
518 | vv->video_minor = vfd->minor; | |
519 | INFO(("%s: registered device video%d [v4l2]\n", | |
520 | dev->name, vfd->minor & 0x1f)); | |
521 | } else { | |
522 | vv->vbi_minor = vfd->minor; | |
523 | INFO(("%s: registered device vbi%d [v4l2]\n", | |
524 | dev->name, vfd->minor & 0x1f)); | |
525 | } | |
526 | ||
527 | *vid = vfd; | |
528 | return 0; | |
529 | } | |
530 | ||
531 | int saa7146_unregister_device(struct video_device **vid, struct saa7146_dev* dev) | |
532 | { | |
533 | struct saa7146_vv *vv = dev->vv_data; | |
534 | ||
535 | DEB_EE(("dev:%p\n",dev)); | |
536 | ||
537 | if( VFL_TYPE_GRABBER == (*vid)->type ) { | |
538 | vv->video_minor = -1; | |
539 | } else { | |
540 | vv->vbi_minor = -1; | |
541 | } | |
542 | ||
543 | video_unregister_device(*vid); | |
544 | *vid = NULL; | |
545 | ||
546 | return 0; | |
547 | } | |
548 | ||
549 | static int __init saa7146_vv_init_module(void) | |
550 | { | |
551 | return 0; | |
552 | } | |
553 | ||
554 | ||
555 | static void __exit saa7146_vv_cleanup_module(void) | |
556 | { | |
557 | } | |
558 | ||
559 | module_init(saa7146_vv_init_module); | |
560 | module_exit(saa7146_vv_cleanup_module); | |
561 | ||
562 | MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); | |
563 | MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); | |
564 | MODULE_LICENSE("GPL"); |