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