Commit | Line | Data |
---|---|---|
3d31c0cb CG |
1 | /* |
2 | * V4L2 AIM - V4L2 Application Interface Module for MostCore | |
3 | * | |
4 | * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG | |
5 | * | |
6 | * This program is distributed in the hope that it will be useful, | |
7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
9 | * GNU General Public License for more details. | |
10 | * | |
11 | * This file is licensed under GPLv2. | |
12 | */ | |
13 | ||
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
15 | ||
16 | #include <linux/module.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/init.h> | |
19 | #include <linux/device.h> | |
20 | #include <linux/suspend.h> | |
21 | #include <linux/videodev2.h> | |
22 | #include <linux/mutex.h> | |
23 | #include <media/v4l2-common.h> | |
24 | #include <media/v4l2-ioctl.h> | |
25 | #include <media/v4l2-event.h> | |
26 | #include <media/v4l2-device.h> | |
27 | #include <media/v4l2-ctrls.h> | |
28 | #include <media/v4l2-fh.h> | |
29 | ||
30 | #include "mostcore.h" | |
31 | ||
3d31c0cb CG |
32 | #define V4L2_AIM_MAX_INPUT 1 |
33 | ||
f13f6981 | 34 | static struct most_aim aim_info; |
3d31c0cb CG |
35 | |
36 | struct most_video_dev { | |
37 | struct most_interface *iface; | |
38 | int ch_idx; | |
39 | struct list_head list; | |
40 | bool mute; | |
41 | ||
42 | struct list_head pending_mbos; | |
43 | spinlock_t list_lock; | |
44 | ||
45 | struct v4l2_device v4l2_dev; | |
46 | atomic_t access_ref; | |
47 | struct video_device *vdev; | |
48 | unsigned int ctrl_input; | |
49 | ||
50 | struct mutex lock; | |
51 | ||
52 | wait_queue_head_t wait_data; | |
53 | }; | |
54 | ||
55 | struct aim_fh { | |
56 | /* must be the first field of this struct! */ | |
57 | struct v4l2_fh fh; | |
58 | struct most_video_dev *mdev; | |
59 | u32 offs; | |
60 | }; | |
61 | ||
3d31c0cb CG |
62 | static struct list_head video_devices = LIST_HEAD_INIT(video_devices); |
63 | static struct spinlock list_lock; | |
3d31c0cb | 64 | |
3d31c0cb CG |
65 | static inline bool data_ready(struct most_video_dev *mdev) |
66 | { | |
67 | return !list_empty(&mdev->pending_mbos); | |
68 | } | |
69 | ||
70 | static inline struct mbo *get_top_mbo(struct most_video_dev *mdev) | |
71 | { | |
72 | return list_first_entry(&mdev->pending_mbos, struct mbo, list); | |
73 | } | |
74 | ||
3d31c0cb CG |
75 | static int aim_vdev_open(struct file *filp) |
76 | { | |
77 | int ret; | |
78 | struct video_device *vdev = video_devdata(filp); | |
79 | struct most_video_dev *mdev = video_drvdata(filp); | |
80 | struct aim_fh *fh; | |
81 | ||
82 | pr_info("aim_vdev_open()\n"); | |
83 | ||
84 | switch (vdev->vfl_type) { | |
85 | case VFL_TYPE_GRABBER: | |
86 | break; | |
87 | default: | |
88 | return -EINVAL; | |
89 | } | |
90 | ||
181a2aa1 | 91 | fh = kzalloc(sizeof(*fh), GFP_KERNEL); |
3d31c0cb CG |
92 | if (!fh) |
93 | return -ENOMEM; | |
94 | ||
95 | if (!atomic_inc_and_test(&mdev->access_ref)) { | |
96 | pr_err("too many clients\n"); | |
97 | ret = -EBUSY; | |
98 | goto err_dec; | |
99 | } | |
100 | ||
101 | fh->mdev = mdev; | |
102 | v4l2_fh_init(&fh->fh, vdev); | |
103 | filp->private_data = fh; | |
104 | ||
105 | v4l2_fh_add(&fh->fh); | |
106 | ||
f13f6981 | 107 | ret = most_start_channel(mdev->iface, mdev->ch_idx, &aim_info); |
3d31c0cb CG |
108 | if (ret) { |
109 | pr_err("most_start_channel() failed\n"); | |
110 | goto err_rm; | |
111 | } | |
112 | ||
113 | return 0; | |
114 | ||
115 | err_rm: | |
116 | v4l2_fh_del(&fh->fh); | |
117 | v4l2_fh_exit(&fh->fh); | |
118 | ||
119 | err_dec: | |
120 | atomic_dec(&mdev->access_ref); | |
121 | kfree(fh); | |
122 | return ret; | |
123 | } | |
124 | ||
125 | static int aim_vdev_close(struct file *filp) | |
126 | { | |
127 | struct aim_fh *fh = filp->private_data; | |
128 | struct most_video_dev *mdev = fh->mdev; | |
129 | struct mbo *mbo, *tmp; | |
130 | ||
131 | pr_info("aim_vdev_close()\n"); | |
132 | ||
133 | /* | |
134 | * We need to put MBOs back before we call most_stop_channel() | |
135 | * to deallocate MBOs. | |
136 | * From the other hand mostcore still calling rx_completion() | |
137 | * to deliver MBOs until most_stop_channel() is called. | |
138 | * Use mute to work around this issue. | |
139 | * This must be implemented in core. | |
140 | */ | |
141 | ||
e494df03 | 142 | spin_lock_irq(&mdev->list_lock); |
3d31c0cb CG |
143 | mdev->mute = true; |
144 | list_for_each_entry_safe(mbo, tmp, &mdev->pending_mbos, list) { | |
145 | list_del(&mbo->list); | |
e494df03 | 146 | spin_unlock_irq(&mdev->list_lock); |
3d31c0cb | 147 | most_put_mbo(mbo); |
e494df03 | 148 | spin_lock_irq(&mdev->list_lock); |
3d31c0cb | 149 | } |
e494df03 | 150 | spin_unlock_irq(&mdev->list_lock); |
f13f6981 | 151 | most_stop_channel(mdev->iface, mdev->ch_idx, &aim_info); |
3d31c0cb CG |
152 | mdev->mute = false; |
153 | ||
154 | v4l2_fh_del(&fh->fh); | |
155 | v4l2_fh_exit(&fh->fh); | |
156 | ||
157 | atomic_dec(&mdev->access_ref); | |
158 | kfree(fh); | |
159 | return 0; | |
160 | } | |
161 | ||
162 | static ssize_t aim_vdev_read(struct file *filp, char __user *buf, | |
163 | size_t count, loff_t *pos) | |
164 | { | |
165 | struct aim_fh *fh = filp->private_data; | |
166 | struct most_video_dev *mdev = fh->mdev; | |
167 | int ret = 0; | |
168 | ||
169 | if (*pos) | |
170 | return -ESPIPE; | |
171 | ||
172 | if (!mdev) | |
173 | return -ENODEV; | |
174 | ||
175 | /* wait for the first buffer */ | |
176 | if (!(filp->f_flags & O_NONBLOCK)) { | |
177 | if (wait_event_interruptible(mdev->wait_data, data_ready(mdev))) | |
178 | return -ERESTARTSYS; | |
179 | } | |
180 | ||
181 | if (!data_ready(mdev)) | |
182 | return -EAGAIN; | |
183 | ||
184 | while (count > 0 && data_ready(mdev)) { | |
185 | struct mbo *const mbo = get_top_mbo(mdev); | |
186 | int const rem = mbo->processed_length - fh->offs; | |
187 | int const cnt = rem < count ? rem : count; | |
188 | ||
189 | if (copy_to_user(buf, mbo->virt_address + fh->offs, cnt)) { | |
190 | pr_err("read: copy_to_user failed\n"); | |
191 | if (!ret) | |
192 | ret = -EFAULT; | |
193 | return ret; | |
194 | } | |
195 | ||
196 | fh->offs += cnt; | |
197 | count -= cnt; | |
198 | buf += cnt; | |
199 | ret += cnt; | |
200 | ||
201 | if (cnt >= rem) { | |
202 | fh->offs = 0; | |
e494df03 | 203 | spin_lock_irq(&mdev->list_lock); |
3d31c0cb | 204 | list_del(&mbo->list); |
e494df03 | 205 | spin_unlock_irq(&mdev->list_lock); |
3d31c0cb CG |
206 | most_put_mbo(mbo); |
207 | } | |
208 | } | |
209 | return ret; | |
210 | } | |
211 | ||
212 | static unsigned int aim_vdev_poll(struct file *filp, poll_table *wait) | |
213 | { | |
214 | struct aim_fh *fh = filp->private_data; | |
215 | struct most_video_dev *mdev = fh->mdev; | |
216 | unsigned int mask = 0; | |
217 | ||
218 | /* only wait if no data is available */ | |
219 | if (!data_ready(mdev)) | |
220 | poll_wait(filp, &mdev->wait_data, wait); | |
221 | if (data_ready(mdev)) | |
222 | mask |= POLLIN | POLLRDNORM; | |
223 | ||
224 | return mask; | |
225 | } | |
226 | ||
227 | static void aim_set_format_struct(struct v4l2_format *f) | |
228 | { | |
229 | f->fmt.pix.width = 8; | |
230 | f->fmt.pix.height = 8; | |
231 | f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; | |
232 | f->fmt.pix.bytesperline = 0; | |
233 | f->fmt.pix.sizeimage = 188 * 2; | |
234 | f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; | |
235 | f->fmt.pix.field = V4L2_FIELD_NONE; | |
236 | f->fmt.pix.priv = 0; | |
237 | } | |
238 | ||
239 | static int aim_set_format(struct most_video_dev *mdev, unsigned int cmd, | |
240 | struct v4l2_format *format) | |
241 | { | |
3d31c0cb CG |
242 | if (format->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG) |
243 | return -EINVAL; | |
244 | ||
245 | if (cmd == VIDIOC_TRY_FMT) | |
246 | return 0; | |
247 | ||
248 | aim_set_format_struct(format); | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
3d31c0cb CG |
253 | static int vidioc_querycap(struct file *file, void *priv, |
254 | struct v4l2_capability *cap) | |
255 | { | |
256 | struct aim_fh *fh = priv; | |
257 | struct most_video_dev *mdev = fh->mdev; | |
258 | ||
259 | pr_info("vidioc_querycap()\n"); | |
260 | ||
261 | strlcpy(cap->driver, "v4l2_most_aim", sizeof(cap->driver)); | |
262 | strlcpy(cap->card, "my_card", sizeof(cap->card)); | |
263 | snprintf(cap->bus_info, sizeof(cap->bus_info), | |
264 | "%s", mdev->iface->description); | |
265 | ||
266 | cap->capabilities = | |
267 | V4L2_CAP_READWRITE | | |
268 | V4L2_CAP_TUNER | | |
269 | V4L2_CAP_VIDEO_CAPTURE; | |
270 | return 0; | |
271 | } | |
272 | ||
273 | static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, | |
274 | struct v4l2_fmtdesc *f) | |
275 | { | |
276 | pr_info("vidioc_enum_fmt_vid_cap() %d\n", f->index); | |
277 | ||
278 | if (f->index) | |
279 | return -EINVAL; | |
280 | ||
281 | strcpy(f->description, "MPEG"); | |
282 | f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
283 | f->flags = V4L2_FMT_FLAG_COMPRESSED; | |
284 | f->pixelformat = V4L2_PIX_FMT_MPEG; | |
285 | ||
286 | return 0; | |
287 | } | |
288 | ||
289 | static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, | |
290 | struct v4l2_format *f) | |
291 | { | |
292 | pr_info("vidioc_g_fmt_vid_cap()\n"); | |
293 | ||
294 | aim_set_format_struct(f); | |
295 | return 0; | |
296 | } | |
297 | ||
298 | static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, | |
299 | struct v4l2_format *f) | |
300 | { | |
301 | struct aim_fh *fh = priv; | |
302 | struct most_video_dev *mdev = fh->mdev; | |
303 | ||
304 | return aim_set_format(mdev, VIDIOC_TRY_FMT, f); | |
305 | } | |
306 | ||
307 | static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, | |
308 | struct v4l2_format *f) | |
309 | { | |
310 | struct aim_fh *fh = priv; | |
311 | struct most_video_dev *mdev = fh->mdev; | |
312 | ||
313 | return aim_set_format(mdev, VIDIOC_S_FMT, f); | |
314 | } | |
315 | ||
316 | static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm) | |
317 | { | |
318 | pr_info("vidioc_g_std()\n"); | |
319 | ||
320 | *norm = V4L2_STD_UNKNOWN; | |
321 | return 0; | |
322 | } | |
323 | ||
324 | static int vidioc_enum_input(struct file *file, void *priv, | |
325 | struct v4l2_input *input) | |
326 | { | |
327 | struct aim_fh *fh = priv; | |
328 | struct most_video_dev *mdev = fh->mdev; | |
329 | ||
330 | if (input->index >= V4L2_AIM_MAX_INPUT) | |
331 | return -EINVAL; | |
332 | ||
333 | strcpy(input->name, "MOST Video"); | |
334 | input->type |= V4L2_INPUT_TYPE_CAMERA; | |
335 | input->audioset = 0; | |
336 | ||
337 | input->std = mdev->vdev->tvnorms; | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
342 | static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) | |
343 | { | |
344 | struct aim_fh *fh = priv; | |
345 | struct most_video_dev *mdev = fh->mdev; | |
346 | *i = mdev->ctrl_input; | |
347 | return 0; | |
348 | } | |
349 | ||
350 | static int vidioc_s_input(struct file *file, void *priv, unsigned int index) | |
351 | { | |
352 | struct aim_fh *fh = priv; | |
353 | struct most_video_dev *mdev = fh->mdev; | |
354 | ||
355 | pr_info("vidioc_s_input(%d)\n", index); | |
356 | ||
357 | if (index >= V4L2_AIM_MAX_INPUT) | |
358 | return -EINVAL; | |
359 | mdev->ctrl_input = index; | |
360 | return 0; | |
361 | } | |
362 | ||
363 | static struct v4l2_file_operations aim_fops = { | |
364 | .owner = THIS_MODULE, | |
365 | .open = aim_vdev_open, | |
366 | .release = aim_vdev_close, | |
367 | .read = aim_vdev_read, | |
368 | .poll = aim_vdev_poll, | |
369 | .unlocked_ioctl = video_ioctl2, | |
370 | }; | |
371 | ||
372 | static const struct v4l2_ioctl_ops video_ioctl_ops = { | |
373 | .vidioc_querycap = vidioc_querycap, | |
374 | .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, | |
375 | .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, | |
376 | .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, | |
377 | .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, | |
378 | .vidioc_g_std = vidioc_g_std, | |
379 | .vidioc_enum_input = vidioc_enum_input, | |
380 | .vidioc_g_input = vidioc_g_input, | |
381 | .vidioc_s_input = vidioc_s_input, | |
382 | }; | |
383 | ||
384 | static const struct video_device aim_videodev_template = { | |
385 | .fops = &aim_fops, | |
386 | .release = video_device_release, | |
387 | .ioctl_ops = &video_ioctl_ops, | |
388 | .tvnorms = V4L2_STD_UNKNOWN, | |
389 | }; | |
390 | ||
391 | /**************************************************************************/ | |
392 | ||
393 | static struct most_video_dev *get_aim_dev( | |
394 | struct most_interface *iface, int channel_idx) | |
395 | { | |
db5a48d5 | 396 | struct most_video_dev *mdev; |
e494df03 | 397 | unsigned long flags; |
3d31c0cb | 398 | |
e494df03 | 399 | spin_lock_irqsave(&list_lock, flags); |
db5a48d5 | 400 | list_for_each_entry(mdev, &video_devices, list) { |
3d31c0cb | 401 | if (mdev->iface == iface && mdev->ch_idx == channel_idx) { |
e494df03 | 402 | spin_unlock_irqrestore(&list_lock, flags); |
3d31c0cb CG |
403 | return mdev; |
404 | } | |
405 | } | |
e494df03 | 406 | spin_unlock_irqrestore(&list_lock, flags); |
9b532df0 | 407 | return NULL; |
3d31c0cb CG |
408 | } |
409 | ||
410 | static int aim_rx_data(struct mbo *mbo) | |
411 | { | |
e494df03 | 412 | unsigned long flags; |
3d31c0cb CG |
413 | struct most_video_dev *mdev = |
414 | get_aim_dev(mbo->ifp, mbo->hdm_channel_id); | |
415 | ||
416 | if (!mdev) | |
417 | return -EIO; | |
418 | ||
e494df03 | 419 | spin_lock_irqsave(&mdev->list_lock, flags); |
3d31c0cb | 420 | if (unlikely(mdev->mute)) { |
e494df03 | 421 | spin_unlock_irqrestore(&mdev->list_lock, flags); |
3d31c0cb CG |
422 | return -EIO; |
423 | } | |
424 | ||
425 | list_add_tail(&mbo->list, &mdev->pending_mbos); | |
e494df03 | 426 | spin_unlock_irqrestore(&mdev->list_lock, flags); |
3d31c0cb CG |
427 | wake_up_interruptible(&mdev->wait_data); |
428 | return 0; | |
429 | } | |
430 | ||
431 | static int aim_register_videodev(struct most_video_dev *mdev) | |
432 | { | |
433 | int retval = -ENOMEM; | |
434 | int ret; | |
435 | ||
436 | pr_info("aim_register_videodev()\n"); | |
437 | ||
438 | init_waitqueue_head(&mdev->wait_data); | |
439 | ||
440 | /* allocate and fill v4l2 video struct */ | |
441 | mdev->vdev = video_device_alloc(); | |
442 | if (!mdev->vdev) | |
443 | return -ENOMEM; | |
444 | ||
445 | /* Fill the video capture device struct */ | |
446 | *mdev->vdev = aim_videodev_template; | |
447 | mdev->vdev->v4l2_dev = &mdev->v4l2_dev; | |
448 | mdev->vdev->lock = &mdev->lock; | |
449 | strcpy(mdev->vdev->name, "most v4l2 aim video"); | |
450 | ||
451 | /* Register the v4l2 device */ | |
452 | video_set_drvdata(mdev->vdev, mdev); | |
453 | retval = video_register_device(mdev->vdev, VFL_TYPE_GRABBER, -1); | |
454 | if (retval != 0) { | |
455 | pr_err("video_register_device failed (%d)\n", retval); | |
456 | ret = -ENODEV; | |
457 | goto err_vbi_dev; | |
458 | } | |
459 | ||
460 | return 0; | |
461 | ||
462 | err_vbi_dev: | |
463 | video_device_release(mdev->vdev); | |
464 | return ret; | |
465 | } | |
466 | ||
467 | static void aim_unregister_videodev(struct most_video_dev *mdev) | |
468 | { | |
469 | pr_info("aim_unregister_videodev()\n"); | |
470 | ||
471 | video_unregister_device(mdev->vdev); | |
472 | } | |
473 | ||
3d31c0cb CG |
474 | static void aim_v4l2_dev_release(struct v4l2_device *v4l2_dev) |
475 | { | |
476 | struct most_video_dev *mdev = | |
477 | container_of(v4l2_dev, struct most_video_dev, v4l2_dev); | |
478 | ||
479 | v4l2_device_unregister(v4l2_dev); | |
480 | kfree(mdev); | |
481 | } | |
482 | ||
483 | static int aim_probe_channel(struct most_interface *iface, int channel_idx, | |
484 | struct most_channel_config *ccfg, | |
485 | struct kobject *parent, char *name) | |
486 | { | |
487 | int ret; | |
488 | struct most_video_dev *mdev = get_aim_dev(iface, channel_idx); | |
489 | ||
490 | pr_info("aim_probe_channel()\n"); | |
491 | ||
492 | if (mdev) { | |
493 | pr_err("channel already linked\n"); | |
494 | return -EEXIST; | |
495 | } | |
496 | ||
497 | if (ccfg->direction != MOST_CH_RX) { | |
498 | pr_err("wrong direction, expect rx\n"); | |
499 | return -EINVAL; | |
500 | } | |
501 | ||
502 | if (ccfg->data_type != MOST_CH_SYNC && | |
503 | ccfg->data_type != MOST_CH_ISOC_AVP) { | |
504 | pr_err("wrong channel type, expect sync or isoc_avp\n"); | |
505 | return -EINVAL; | |
506 | } | |
507 | ||
508 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); | |
509 | if (!mdev) | |
510 | return -ENOMEM; | |
511 | ||
512 | mutex_init(&mdev->lock); | |
513 | atomic_set(&mdev->access_ref, -1); | |
514 | spin_lock_init(&mdev->list_lock); | |
515 | INIT_LIST_HEAD(&mdev->pending_mbos); | |
516 | mdev->iface = iface; | |
517 | mdev->ch_idx = channel_idx; | |
518 | mdev->v4l2_dev.release = aim_v4l2_dev_release; | |
519 | ||
520 | /* Create the v4l2_device */ | |
521 | strlcpy(mdev->v4l2_dev.name, "most_video_device", | |
522 | sizeof(mdev->v4l2_dev.name)); | |
523 | ret = v4l2_device_register(NULL, &mdev->v4l2_dev); | |
524 | if (ret) { | |
525 | pr_err("v4l2_device_register() failed\n"); | |
526 | kfree(mdev); | |
527 | return ret; | |
528 | } | |
529 | ||
530 | ret = aim_register_videodev(mdev); | |
531 | if (ret) | |
532 | goto err_unreg; | |
533 | ||
e494df03 | 534 | spin_lock_irq(&list_lock); |
3d31c0cb | 535 | list_add(&mdev->list, &video_devices); |
e494df03 | 536 | spin_unlock_irq(&list_lock); |
3d31c0cb CG |
537 | return 0; |
538 | ||
539 | err_unreg: | |
540 | v4l2_device_disconnect(&mdev->v4l2_dev); | |
541 | v4l2_device_put(&mdev->v4l2_dev); | |
542 | return ret; | |
543 | } | |
544 | ||
545 | static int aim_disconnect_channel(struct most_interface *iface, | |
546 | int channel_idx) | |
547 | { | |
548 | struct most_video_dev *mdev = get_aim_dev(iface, channel_idx); | |
549 | ||
550 | pr_info("aim_disconnect_channel()\n"); | |
551 | ||
552 | if (!mdev) { | |
553 | pr_err("no such channel is linked\n"); | |
554 | return -ENOENT; | |
555 | } | |
556 | ||
e494df03 | 557 | spin_lock_irq(&list_lock); |
3d31c0cb | 558 | list_del(&mdev->list); |
e494df03 | 559 | spin_unlock_irq(&list_lock); |
3d31c0cb CG |
560 | |
561 | aim_unregister_videodev(mdev); | |
562 | v4l2_device_disconnect(&mdev->v4l2_dev); | |
563 | v4l2_device_put(&mdev->v4l2_dev); | |
564 | return 0; | |
565 | } | |
566 | ||
ec5c00af CG |
567 | static struct most_aim aim_info = { |
568 | .name = "v4l", | |
569 | .probe_channel = aim_probe_channel, | |
570 | .disconnect_channel = aim_disconnect_channel, | |
571 | .rx_completion = aim_rx_data, | |
572 | }; | |
573 | ||
3d31c0cb CG |
574 | static int __init aim_init(void) |
575 | { | |
576 | spin_lock_init(&list_lock); | |
3d31c0cb CG |
577 | return most_register_aim(&aim_info); |
578 | } | |
579 | ||
580 | static void __exit aim_exit(void) | |
581 | { | |
582 | struct most_video_dev *mdev, *tmp; | |
583 | ||
584 | /* | |
585 | * As the mostcore currently doesn't call disconnect_channel() | |
586 | * for linked channels while we call most_deregister_aim() | |
587 | * we simulate this call here. | |
588 | * This must be fixed in core. | |
589 | */ | |
e494df03 | 590 | spin_lock_irq(&list_lock); |
3d31c0cb CG |
591 | list_for_each_entry_safe(mdev, tmp, &video_devices, list) { |
592 | list_del(&mdev->list); | |
e494df03 | 593 | spin_unlock_irq(&list_lock); |
3d31c0cb CG |
594 | |
595 | aim_unregister_videodev(mdev); | |
596 | v4l2_device_disconnect(&mdev->v4l2_dev); | |
597 | v4l2_device_put(&mdev->v4l2_dev); | |
e494df03 | 598 | spin_lock_irq(&list_lock); |
3d31c0cb | 599 | } |
e494df03 | 600 | spin_unlock_irq(&list_lock); |
3d31c0cb CG |
601 | |
602 | most_deregister_aim(&aim_info); | |
603 | BUG_ON(!list_empty(&video_devices)); | |
604 | } | |
605 | ||
606 | module_init(aim_init); | |
607 | module_exit(aim_exit); | |
608 | ||
609 | MODULE_DESCRIPTION("V4L2 Application Interface Module for MostCore"); | |
610 | MODULE_AUTHOR("Andrey Shvetsov <andrey.shvetsov@k2l.de>"); | |
611 | MODULE_LICENSE("GPL"); |