]>
Commit | Line | Data |
---|---|---|
e1302912 SL |
1 | /* |
2 | * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC | |
3 | * | |
4 | * Copyright (c) 2016 Mentor Graphics Inc. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | #include <linux/delay.h> | |
12 | #include <linux/fs.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of_platform.h> | |
15 | #include <linux/pinctrl/consumer.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/sched.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/spinlock.h> | |
20 | #include <linux/timer.h> | |
21 | #include <media/v4l2-ctrls.h> | |
22 | #include <media/v4l2-event.h> | |
23 | #include <media/v4l2-ioctl.h> | |
24 | #include <media/v4l2-mc.h> | |
25 | #include <video/imx-ipu-v3.h> | |
26 | #include <media/imx.h> | |
27 | #include "imx-media.h" | |
28 | ||
29 | static inline struct imx_media_dev *notifier2dev(struct v4l2_async_notifier *n) | |
30 | { | |
31 | return container_of(n, struct imx_media_dev, subdev_notifier); | |
32 | } | |
33 | ||
34 | /* | |
35 | * Find a subdev by device node or device name. This is called during | |
36 | * driver load to form the async subdev list and bind them. | |
37 | */ | |
38 | struct imx_media_subdev * | |
39 | imx_media_find_async_subdev(struct imx_media_dev *imxmd, | |
40 | struct device_node *np, | |
41 | const char *devname) | |
42 | { | |
43 | struct fwnode_handle *fwnode = np ? of_fwnode_handle(np) : NULL; | |
44 | struct imx_media_subdev *imxsd; | |
45 | int i; | |
46 | ||
47 | for (i = 0; i < imxmd->subdev_notifier.num_subdevs; i++) { | |
48 | imxsd = &imxmd->subdev[i]; | |
49 | switch (imxsd->asd.match_type) { | |
50 | case V4L2_ASYNC_MATCH_FWNODE: | |
51 | if (fwnode && imxsd->asd.match.fwnode.fwnode == fwnode) | |
52 | return imxsd; | |
53 | break; | |
54 | case V4L2_ASYNC_MATCH_DEVNAME: | |
55 | if (devname && | |
56 | !strcmp(imxsd->asd.match.device_name.name, devname)) | |
57 | return imxsd; | |
58 | break; | |
59 | default: | |
60 | break; | |
61 | } | |
62 | } | |
63 | ||
64 | return NULL; | |
65 | } | |
66 | ||
67 | ||
68 | /* | |
69 | * Adds a subdev to the async subdev list. If np is non-NULL, adds | |
70 | * the async as a V4L2_ASYNC_MATCH_FWNODE match type, otherwise as | |
71 | * a V4L2_ASYNC_MATCH_DEVNAME match type using the dev_name of the | |
72 | * given platform_device. This is called during driver load when | |
73 | * forming the async subdev list. | |
74 | */ | |
75 | struct imx_media_subdev * | |
76 | imx_media_add_async_subdev(struct imx_media_dev *imxmd, | |
77 | struct device_node *np, | |
78 | struct platform_device *pdev) | |
79 | { | |
80 | struct imx_media_subdev *imxsd; | |
81 | struct v4l2_async_subdev *asd; | |
82 | const char *devname = NULL; | |
83 | int sd_idx; | |
84 | ||
85 | mutex_lock(&imxmd->mutex); | |
86 | ||
87 | if (pdev) | |
88 | devname = dev_name(&pdev->dev); | |
89 | ||
0b2e9e79 | 90 | /* return -EEXIST if this subdev already added */ |
e1302912 SL |
91 | if (imx_media_find_async_subdev(imxmd, np, devname)) { |
92 | dev_dbg(imxmd->md.dev, "%s: already added %s\n", | |
93 | __func__, np ? np->name : devname); | |
0b2e9e79 | 94 | imxsd = ERR_PTR(-EEXIST); |
e1302912 SL |
95 | goto out; |
96 | } | |
97 | ||
98 | sd_idx = imxmd->subdev_notifier.num_subdevs; | |
99 | if (sd_idx >= IMX_MEDIA_MAX_SUBDEVS) { | |
100 | dev_err(imxmd->md.dev, "%s: too many subdevs! can't add %s\n", | |
101 | __func__, np ? np->name : devname); | |
102 | imxsd = ERR_PTR(-ENOSPC); | |
103 | goto out; | |
104 | } | |
105 | ||
106 | imxsd = &imxmd->subdev[sd_idx]; | |
107 | ||
108 | asd = &imxsd->asd; | |
109 | if (np) { | |
110 | asd->match_type = V4L2_ASYNC_MATCH_FWNODE; | |
111 | asd->match.fwnode.fwnode = of_fwnode_handle(np); | |
112 | } else { | |
113 | asd->match_type = V4L2_ASYNC_MATCH_DEVNAME; | |
114 | strncpy(imxsd->devname, devname, sizeof(imxsd->devname)); | |
115 | asd->match.device_name.name = imxsd->devname; | |
116 | imxsd->pdev = pdev; | |
117 | } | |
118 | ||
119 | imxmd->async_ptrs[sd_idx] = asd; | |
120 | imxmd->subdev_notifier.num_subdevs++; | |
121 | ||
122 | dev_dbg(imxmd->md.dev, "%s: added %s, match type %s\n", | |
123 | __func__, np ? np->name : devname, np ? "FWNODE" : "DEVNAME"); | |
124 | ||
125 | out: | |
126 | mutex_unlock(&imxmd->mutex); | |
127 | return imxsd; | |
128 | } | |
129 | ||
130 | /* | |
131 | * Adds an imx-media link to a subdev pad's link list. This is called | |
132 | * during driver load when forming the links between subdevs. | |
133 | * | |
134 | * @pad: the local pad | |
135 | * @remote_node: the device node of the remote subdev | |
136 | * @remote_devname: the device name of the remote subdev | |
137 | * @local_pad: local pad index | |
138 | * @remote_pad: remote pad index | |
139 | */ | |
140 | int imx_media_add_pad_link(struct imx_media_dev *imxmd, | |
141 | struct imx_media_pad *pad, | |
142 | struct device_node *remote_node, | |
143 | const char *remote_devname, | |
144 | int local_pad, int remote_pad) | |
145 | { | |
146 | struct imx_media_link *link; | |
147 | int link_idx, ret = 0; | |
148 | ||
149 | mutex_lock(&imxmd->mutex); | |
150 | ||
151 | link_idx = pad->num_links; | |
152 | if (link_idx >= IMX_MEDIA_MAX_LINKS) { | |
153 | dev_err(imxmd->md.dev, "%s: too many links!\n", __func__); | |
154 | ret = -ENOSPC; | |
155 | goto out; | |
156 | } | |
157 | ||
158 | link = &pad->link[link_idx]; | |
159 | ||
160 | link->remote_sd_node = remote_node; | |
161 | if (remote_devname) | |
162 | strncpy(link->remote_devname, remote_devname, | |
163 | sizeof(link->remote_devname)); | |
164 | ||
165 | link->local_pad = local_pad; | |
166 | link->remote_pad = remote_pad; | |
167 | ||
168 | pad->num_links++; | |
169 | out: | |
170 | mutex_unlock(&imxmd->mutex); | |
171 | return ret; | |
172 | } | |
173 | ||
174 | /* | |
175 | * get IPU from this CSI and add it to the list of IPUs | |
176 | * the media driver will control. | |
177 | */ | |
178 | static int imx_media_get_ipu(struct imx_media_dev *imxmd, | |
179 | struct v4l2_subdev *csi_sd) | |
180 | { | |
181 | struct ipu_soc *ipu; | |
182 | int ipu_id; | |
183 | ||
184 | ipu = dev_get_drvdata(csi_sd->dev->parent); | |
185 | if (!ipu) { | |
186 | v4l2_err(&imxmd->v4l2_dev, | |
187 | "CSI %s has no parent IPU!\n", csi_sd->name); | |
188 | return -ENODEV; | |
189 | } | |
190 | ||
191 | ipu_id = ipu_get_num(ipu); | |
192 | if (ipu_id > 1) { | |
193 | v4l2_err(&imxmd->v4l2_dev, "invalid IPU id %d!\n", ipu_id); | |
194 | return -ENODEV; | |
195 | } | |
196 | ||
197 | if (!imxmd->ipu[ipu_id]) | |
198 | imxmd->ipu[ipu_id] = ipu; | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | /* async subdev bound notifier */ | |
204 | static int imx_media_subdev_bound(struct v4l2_async_notifier *notifier, | |
205 | struct v4l2_subdev *sd, | |
206 | struct v4l2_async_subdev *asd) | |
207 | { | |
208 | struct imx_media_dev *imxmd = notifier2dev(notifier); | |
209 | struct device_node *np = to_of_node(sd->fwnode); | |
210 | struct imx_media_subdev *imxsd; | |
211 | int ret = 0; | |
212 | ||
213 | mutex_lock(&imxmd->mutex); | |
214 | ||
215 | imxsd = imx_media_find_async_subdev(imxmd, np, dev_name(sd->dev)); | |
216 | if (!imxsd) { | |
217 | ret = -EINVAL; | |
218 | goto out; | |
219 | } | |
220 | ||
221 | if (sd->grp_id & IMX_MEDIA_GRP_ID_CSI) { | |
222 | ret = imx_media_get_ipu(imxmd, sd); | |
223 | if (ret) | |
224 | goto out_unlock; | |
225 | } else if (sd->entity.function == MEDIA_ENT_F_VID_MUX) { | |
226 | /* this is a video mux */ | |
227 | sd->grp_id = IMX_MEDIA_GRP_ID_VIDMUX; | |
228 | } else if (imxsd->num_sink_pads == 0) { | |
229 | /* | |
230 | * this is an original source of video frames, it | |
231 | * could be a camera sensor, an analog decoder, or | |
232 | * a bridge device (HDMI -> MIPI CSI-2 for example). | |
233 | * This group ID is used to locate the entity that | |
234 | * is the original source of video in a pipeline. | |
235 | */ | |
236 | sd->grp_id = IMX_MEDIA_GRP_ID_SENSOR; | |
237 | } | |
238 | ||
239 | /* attach the subdev */ | |
240 | imxsd->sd = sd; | |
241 | out: | |
242 | if (ret) | |
243 | v4l2_warn(&imxmd->v4l2_dev, | |
244 | "Received unknown subdev %s\n", sd->name); | |
245 | else | |
246 | v4l2_info(&imxmd->v4l2_dev, | |
247 | "Registered subdev %s\n", sd->name); | |
248 | ||
249 | out_unlock: | |
250 | mutex_unlock(&imxmd->mutex); | |
251 | return ret; | |
252 | } | |
253 | ||
254 | /* | |
255 | * Create a single source->sink media link given a subdev and a single | |
256 | * link from one of its source pads. Called after all subdevs have | |
257 | * registered. | |
258 | */ | |
259 | static int imx_media_create_link(struct imx_media_dev *imxmd, | |
260 | struct imx_media_subdev *src, | |
261 | struct imx_media_link *link) | |
262 | { | |
263 | struct imx_media_subdev *sink; | |
264 | u16 source_pad, sink_pad; | |
265 | int ret; | |
266 | ||
267 | sink = imx_media_find_async_subdev(imxmd, link->remote_sd_node, | |
268 | link->remote_devname); | |
269 | if (!sink) { | |
270 | v4l2_warn(&imxmd->v4l2_dev, "%s: no sink for %s:%d\n", | |
271 | __func__, src->sd->name, link->local_pad); | |
272 | return 0; | |
273 | } | |
274 | ||
275 | source_pad = link->local_pad; | |
276 | sink_pad = link->remote_pad; | |
277 | ||
278 | v4l2_info(&imxmd->v4l2_dev, "%s: %s:%d -> %s:%d\n", __func__, | |
279 | src->sd->name, source_pad, sink->sd->name, sink_pad); | |
280 | ||
281 | ret = media_create_pad_link(&src->sd->entity, source_pad, | |
282 | &sink->sd->entity, sink_pad, 0); | |
283 | if (ret) | |
284 | v4l2_err(&imxmd->v4l2_dev, | |
285 | "create_pad_link failed: %d\n", ret); | |
286 | ||
287 | return ret; | |
288 | } | |
289 | ||
290 | /* | |
291 | * create the media links from all imx-media pads and their links. | |
292 | * Called after all subdevs have registered. | |
293 | */ | |
294 | static int imx_media_create_links(struct imx_media_dev *imxmd) | |
295 | { | |
296 | struct imx_media_subdev *imxsd; | |
297 | struct imx_media_link *link; | |
298 | struct imx_media_pad *pad; | |
299 | int num_pads, i, j, k; | |
300 | int ret = 0; | |
301 | ||
302 | for (i = 0; i < imxmd->num_subdevs; i++) { | |
303 | imxsd = &imxmd->subdev[i]; | |
304 | num_pads = imxsd->num_sink_pads + imxsd->num_src_pads; | |
305 | ||
306 | for (j = 0; j < num_pads; j++) { | |
307 | pad = &imxsd->pad[j]; | |
308 | ||
309 | /* only create the source->sink links */ | |
310 | if (!(pad->pad.flags & MEDIA_PAD_FL_SOURCE)) | |
311 | continue; | |
312 | ||
313 | for (k = 0; k < pad->num_links; k++) { | |
314 | link = &pad->link[k]; | |
315 | ||
316 | ret = imx_media_create_link(imxmd, imxsd, link); | |
317 | if (ret) | |
318 | goto out; | |
319 | } | |
320 | } | |
321 | } | |
322 | ||
323 | out: | |
324 | return ret; | |
325 | } | |
326 | ||
327 | /* | |
328 | * adds given video device to given imx-media source pad vdev list. | |
329 | * Continues upstream from the pad entity's sink pads. | |
330 | */ | |
331 | static int imx_media_add_vdev_to_pad(struct imx_media_dev *imxmd, | |
332 | struct imx_media_video_dev *vdev, | |
333 | struct media_pad *srcpad) | |
334 | { | |
335 | struct media_entity *entity = srcpad->entity; | |
336 | struct imx_media_subdev *imxsd; | |
337 | struct imx_media_pad *imxpad; | |
338 | struct media_link *link; | |
339 | struct v4l2_subdev *sd; | |
340 | int i, vdev_idx, ret; | |
341 | ||
342 | /* skip this entity if not a v4l2_subdev */ | |
343 | if (!is_media_entity_v4l2_subdev(entity)) | |
344 | return 0; | |
345 | ||
346 | sd = media_entity_to_v4l2_subdev(entity); | |
347 | imxsd = imx_media_find_subdev_by_sd(imxmd, sd); | |
348 | if (IS_ERR(imxsd)) | |
349 | return PTR_ERR(imxsd); | |
350 | ||
351 | imxpad = &imxsd->pad[srcpad->index]; | |
352 | vdev_idx = imxpad->num_vdevs; | |
353 | ||
354 | /* just return if we've been here before */ | |
355 | for (i = 0; i < vdev_idx; i++) | |
356 | if (vdev == imxpad->vdev[i]) | |
357 | return 0; | |
358 | ||
359 | if (vdev_idx >= IMX_MEDIA_MAX_VDEVS) { | |
360 | dev_err(imxmd->md.dev, "can't add %s to pad %s:%u\n", | |
361 | vdev->vfd->entity.name, entity->name, srcpad->index); | |
362 | return -ENOSPC; | |
363 | } | |
364 | ||
365 | dev_dbg(imxmd->md.dev, "adding %s to pad %s:%u\n", | |
366 | vdev->vfd->entity.name, entity->name, srcpad->index); | |
367 | imxpad->vdev[vdev_idx] = vdev; | |
368 | imxpad->num_vdevs++; | |
369 | ||
370 | /* move upstream from this entity's sink pads */ | |
371 | for (i = 0; i < entity->num_pads; i++) { | |
372 | struct media_pad *pad = &entity->pads[i]; | |
373 | ||
374 | if (!(pad->flags & MEDIA_PAD_FL_SINK)) | |
375 | continue; | |
376 | ||
377 | list_for_each_entry(link, &entity->links, list) { | |
378 | if (link->sink != pad) | |
379 | continue; | |
380 | ret = imx_media_add_vdev_to_pad(imxmd, vdev, | |
381 | link->source); | |
382 | if (ret) | |
383 | return ret; | |
384 | } | |
385 | } | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
390 | /* form the vdev lists in all imx-media source pads */ | |
391 | static int imx_media_create_pad_vdev_lists(struct imx_media_dev *imxmd) | |
392 | { | |
393 | struct imx_media_video_dev *vdev; | |
394 | struct media_link *link; | |
395 | int i, ret; | |
396 | ||
397 | for (i = 0; i < imxmd->num_vdevs; i++) { | |
398 | vdev = imxmd->vdev[i]; | |
399 | link = list_first_entry(&vdev->vfd->entity.links, | |
400 | struct media_link, list); | |
401 | ret = imx_media_add_vdev_to_pad(imxmd, vdev, link->source); | |
402 | if (ret) | |
81b79c71 | 403 | return ret; |
e1302912 SL |
404 | } |
405 | ||
81b79c71 | 406 | return 0; |
e1302912 SL |
407 | } |
408 | ||
409 | /* async subdev complete notifier */ | |
410 | static int imx_media_probe_complete(struct v4l2_async_notifier *notifier) | |
411 | { | |
412 | struct imx_media_dev *imxmd = notifier2dev(notifier); | |
413 | int i, ret; | |
414 | ||
415 | mutex_lock(&imxmd->mutex); | |
416 | ||
417 | /* make sure all subdevs were bound */ | |
418 | for (i = 0; i < imxmd->num_subdevs; i++) { | |
419 | if (!imxmd->subdev[i].sd) { | |
420 | v4l2_err(&imxmd->v4l2_dev, "unbound subdev!\n"); | |
421 | ret = -ENODEV; | |
422 | goto unlock; | |
423 | } | |
424 | } | |
425 | ||
426 | ret = imx_media_create_links(imxmd); | |
427 | if (ret) | |
428 | goto unlock; | |
429 | ||
430 | ret = imx_media_create_pad_vdev_lists(imxmd); | |
431 | if (ret) | |
432 | goto unlock; | |
433 | ||
434 | ret = v4l2_device_register_subdev_nodes(&imxmd->v4l2_dev); | |
435 | unlock: | |
436 | mutex_unlock(&imxmd->mutex); | |
437 | if (ret) | |
438 | return ret; | |
439 | ||
440 | return media_device_register(&imxmd->md); | |
441 | } | |
442 | ||
443 | /* | |
444 | * adds controls to a video device from an entity subdevice. | |
445 | * Continues upstream from the entity's sink pads. | |
446 | */ | |
447 | static int imx_media_inherit_controls(struct imx_media_dev *imxmd, | |
448 | struct video_device *vfd, | |
449 | struct media_entity *entity) | |
450 | { | |
451 | int i, ret = 0; | |
452 | ||
453 | if (is_media_entity_v4l2_subdev(entity)) { | |
454 | struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); | |
455 | ||
456 | dev_dbg(imxmd->md.dev, | |
457 | "adding controls to %s from %s\n", | |
458 | vfd->entity.name, sd->entity.name); | |
459 | ||
460 | ret = v4l2_ctrl_add_handler(vfd->ctrl_handler, | |
461 | sd->ctrl_handler, | |
462 | NULL); | |
463 | if (ret) | |
464 | return ret; | |
465 | } | |
466 | ||
467 | /* move upstream */ | |
468 | for (i = 0; i < entity->num_pads; i++) { | |
469 | struct media_pad *pad, *spad = &entity->pads[i]; | |
470 | ||
471 | if (!(spad->flags & MEDIA_PAD_FL_SINK)) | |
472 | continue; | |
473 | ||
474 | pad = media_entity_remote_pad(spad); | |
475 | if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) | |
476 | continue; | |
477 | ||
478 | ret = imx_media_inherit_controls(imxmd, vfd, pad->entity); | |
479 | if (ret) | |
480 | break; | |
481 | } | |
482 | ||
483 | return ret; | |
484 | } | |
485 | ||
486 | static int imx_media_link_notify(struct media_link *link, u32 flags, | |
487 | unsigned int notification) | |
488 | { | |
489 | struct media_entity *source = link->source->entity; | |
490 | struct imx_media_subdev *imxsd; | |
491 | struct imx_media_pad *imxpad; | |
492 | struct imx_media_dev *imxmd; | |
493 | struct video_device *vfd; | |
494 | struct v4l2_subdev *sd; | |
495 | int i, pad_idx, ret; | |
496 | ||
497 | ret = v4l2_pipeline_link_notify(link, flags, notification); | |
498 | if (ret) | |
499 | return ret; | |
500 | ||
501 | /* don't bother if source is not a subdev */ | |
502 | if (!is_media_entity_v4l2_subdev(source)) | |
503 | return 0; | |
504 | ||
505 | sd = media_entity_to_v4l2_subdev(source); | |
506 | pad_idx = link->source->index; | |
507 | ||
508 | imxmd = dev_get_drvdata(sd->v4l2_dev->dev); | |
509 | ||
510 | imxsd = imx_media_find_subdev_by_sd(imxmd, sd); | |
511 | if (IS_ERR(imxsd)) | |
512 | return PTR_ERR(imxsd); | |
513 | imxpad = &imxsd->pad[pad_idx]; | |
514 | ||
515 | /* | |
516 | * Before disabling a link, reset controls for all video | |
517 | * devices reachable from this link. | |
518 | * | |
519 | * After enabling a link, refresh controls for all video | |
520 | * devices reachable from this link. | |
521 | */ | |
522 | if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH && | |
523 | !(flags & MEDIA_LNK_FL_ENABLED)) { | |
524 | for (i = 0; i < imxpad->num_vdevs; i++) { | |
525 | vfd = imxpad->vdev[i]->vfd; | |
526 | dev_dbg(imxmd->md.dev, | |
527 | "reset controls for %s\n", | |
528 | vfd->entity.name); | |
529 | v4l2_ctrl_handler_free(vfd->ctrl_handler); | |
530 | v4l2_ctrl_handler_init(vfd->ctrl_handler, 0); | |
531 | } | |
532 | } else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && | |
533 | (link->flags & MEDIA_LNK_FL_ENABLED)) { | |
534 | for (i = 0; i < imxpad->num_vdevs; i++) { | |
535 | vfd = imxpad->vdev[i]->vfd; | |
536 | dev_dbg(imxmd->md.dev, | |
537 | "refresh controls for %s\n", | |
538 | vfd->entity.name); | |
539 | ret = imx_media_inherit_controls(imxmd, vfd, | |
540 | &vfd->entity); | |
541 | if (ret) | |
542 | break; | |
543 | } | |
544 | } | |
545 | ||
546 | return ret; | |
547 | } | |
548 | ||
549 | static const struct media_device_ops imx_media_md_ops = { | |
550 | .link_notify = imx_media_link_notify, | |
551 | }; | |
552 | ||
553 | static int imx_media_probe(struct platform_device *pdev) | |
554 | { | |
555 | struct device *dev = &pdev->dev; | |
556 | struct device_node *node = dev->of_node; | |
557 | struct imx_media_subdev *csi[4] = {0}; | |
558 | struct imx_media_dev *imxmd; | |
559 | int ret; | |
560 | ||
561 | imxmd = devm_kzalloc(dev, sizeof(*imxmd), GFP_KERNEL); | |
562 | if (!imxmd) | |
563 | return -ENOMEM; | |
564 | ||
565 | dev_set_drvdata(dev, imxmd); | |
566 | ||
567 | strlcpy(imxmd->md.model, "imx-media", sizeof(imxmd->md.model)); | |
568 | imxmd->md.ops = &imx_media_md_ops; | |
569 | imxmd->md.dev = dev; | |
570 | ||
571 | mutex_init(&imxmd->mutex); | |
572 | ||
573 | imxmd->v4l2_dev.mdev = &imxmd->md; | |
574 | strlcpy(imxmd->v4l2_dev.name, "imx-media", | |
575 | sizeof(imxmd->v4l2_dev.name)); | |
576 | ||
577 | media_device_init(&imxmd->md); | |
578 | ||
579 | ret = v4l2_device_register(dev, &imxmd->v4l2_dev); | |
580 | if (ret < 0) { | |
581 | v4l2_err(&imxmd->v4l2_dev, | |
582 | "Failed to register v4l2_device: %d\n", ret); | |
583 | goto cleanup; | |
584 | } | |
585 | ||
586 | dev_set_drvdata(imxmd->v4l2_dev.dev, imxmd); | |
587 | ||
588 | ret = imx_media_of_parse(imxmd, &csi, node); | |
589 | if (ret) { | |
590 | v4l2_err(&imxmd->v4l2_dev, | |
591 | "imx_media_of_parse failed with %d\n", ret); | |
592 | goto unreg_dev; | |
593 | } | |
594 | ||
595 | ret = imx_media_add_internal_subdevs(imxmd, csi); | |
596 | if (ret) { | |
597 | v4l2_err(&imxmd->v4l2_dev, | |
598 | "add_internal_subdevs failed with %d\n", ret); | |
599 | goto unreg_dev; | |
600 | } | |
601 | ||
602 | /* no subdevs? just bail */ | |
603 | imxmd->num_subdevs = imxmd->subdev_notifier.num_subdevs; | |
604 | if (imxmd->num_subdevs == 0) { | |
605 | ret = -ENODEV; | |
606 | goto unreg_dev; | |
607 | } | |
608 | ||
609 | /* prepare the async subdev notifier and register it */ | |
610 | imxmd->subdev_notifier.subdevs = imxmd->async_ptrs; | |
611 | imxmd->subdev_notifier.bound = imx_media_subdev_bound; | |
612 | imxmd->subdev_notifier.complete = imx_media_probe_complete; | |
613 | ret = v4l2_async_notifier_register(&imxmd->v4l2_dev, | |
614 | &imxmd->subdev_notifier); | |
615 | if (ret) { | |
616 | v4l2_err(&imxmd->v4l2_dev, | |
617 | "v4l2_async_notifier_register failed with %d\n", ret); | |
618 | goto del_int; | |
619 | } | |
620 | ||
621 | return 0; | |
622 | ||
623 | del_int: | |
624 | imx_media_remove_internal_subdevs(imxmd); | |
625 | unreg_dev: | |
626 | v4l2_device_unregister(&imxmd->v4l2_dev); | |
627 | cleanup: | |
628 | media_device_cleanup(&imxmd->md); | |
629 | return ret; | |
630 | } | |
631 | ||
632 | static int imx_media_remove(struct platform_device *pdev) | |
633 | { | |
634 | struct imx_media_dev *imxmd = | |
635 | (struct imx_media_dev *)platform_get_drvdata(pdev); | |
636 | ||
637 | v4l2_info(&imxmd->v4l2_dev, "Removing imx-media\n"); | |
638 | ||
639 | v4l2_async_notifier_unregister(&imxmd->subdev_notifier); | |
640 | imx_media_remove_internal_subdevs(imxmd); | |
641 | v4l2_device_unregister(&imxmd->v4l2_dev); | |
642 | media_device_unregister(&imxmd->md); | |
643 | media_device_cleanup(&imxmd->md); | |
644 | ||
645 | return 0; | |
646 | } | |
647 | ||
648 | static const struct of_device_id imx_media_dt_ids[] = { | |
649 | { .compatible = "fsl,imx-capture-subsystem" }, | |
650 | { /* sentinel */ } | |
651 | }; | |
652 | MODULE_DEVICE_TABLE(of, imx_media_dt_ids); | |
653 | ||
654 | static struct platform_driver imx_media_pdrv = { | |
655 | .probe = imx_media_probe, | |
656 | .remove = imx_media_remove, | |
657 | .driver = { | |
658 | .name = "imx-media", | |
659 | .of_match_table = imx_media_dt_ids, | |
660 | }, | |
661 | }; | |
662 | ||
663 | module_platform_driver(imx_media_pdrv); | |
664 | ||
665 | MODULE_DESCRIPTION("i.MX5/6 v4l2 media controller driver"); | |
666 | MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); | |
667 | MODULE_LICENSE("GPL"); |