| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * video stream multiplexer controlled via mux control |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de> |
|---|
| 5 | 6 | * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or |
|---|
| 8 | | - * modify it under the terms of the GNU General Public License |
|---|
| 9 | | - * as published by the Free Software Foundation; either version 2 |
|---|
| 10 | | - * of the License, or (at your option) any later version. |
|---|
| 11 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 12 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | | - * GNU General Public License for more details. |
|---|
| 15 | 7 | */ |
|---|
| 16 | 8 | |
|---|
| 17 | 9 | #include <linux/err.h> |
|---|
| .. | .. |
|---|
| 21 | 13 | #include <linux/of.h> |
|---|
| 22 | 14 | #include <linux/of_graph.h> |
|---|
| 23 | 15 | #include <linux/platform_device.h> |
|---|
| 16 | +#include <linux/slab.h> |
|---|
| 24 | 17 | #include <media/v4l2-async.h> |
|---|
| 25 | 18 | #include <media/v4l2-device.h> |
|---|
| 19 | +#include <media/v4l2-fwnode.h> |
|---|
| 20 | +#include <media/v4l2-mc.h> |
|---|
| 26 | 21 | #include <media/v4l2-subdev.h> |
|---|
| 27 | 22 | |
|---|
| 28 | 23 | struct video_mux { |
|---|
| 29 | 24 | struct v4l2_subdev subdev; |
|---|
| 25 | + struct v4l2_async_notifier notifier; |
|---|
| 30 | 26 | struct media_pad *pads; |
|---|
| 31 | 27 | struct v4l2_mbus_framefmt *format_mbus; |
|---|
| 32 | 28 | struct mux_control *mux; |
|---|
| .. | .. |
|---|
| 40 | 36 | .code = MEDIA_BUS_FMT_Y8_1X8, |
|---|
| 41 | 37 | .field = V4L2_FIELD_NONE, |
|---|
| 42 | 38 | }; |
|---|
| 39 | + |
|---|
| 40 | +static inline struct video_mux * |
|---|
| 41 | +notifier_to_video_mux(struct v4l2_async_notifier *n) |
|---|
| 42 | +{ |
|---|
| 43 | + return container_of(n, struct video_mux, notifier); |
|---|
| 44 | +} |
|---|
| 43 | 45 | |
|---|
| 44 | 46 | static inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd) |
|---|
| 45 | 47 | { |
|---|
| .. | .. |
|---|
| 102 | 104 | static const struct media_entity_operations video_mux_ops = { |
|---|
| 103 | 105 | .link_setup = video_mux_link_setup, |
|---|
| 104 | 106 | .link_validate = v4l2_subdev_link_validate, |
|---|
| 107 | + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, |
|---|
| 105 | 108 | }; |
|---|
| 106 | 109 | |
|---|
| 107 | 110 | static int video_mux_s_stream(struct v4l2_subdev *sd, int enable) |
|---|
| .. | .. |
|---|
| 261 | 264 | case MEDIA_BUS_FMT_UYYVYY16_0_5X48: |
|---|
| 262 | 265 | case MEDIA_BUS_FMT_JPEG_1X8: |
|---|
| 263 | 266 | case MEDIA_BUS_FMT_AHSV8888_1X32: |
|---|
| 267 | + case MEDIA_BUS_FMT_SBGGR8_1X8: |
|---|
| 268 | + case MEDIA_BUS_FMT_SGBRG8_1X8: |
|---|
| 269 | + case MEDIA_BUS_FMT_SGRBG8_1X8: |
|---|
| 270 | + case MEDIA_BUS_FMT_SRGGB8_1X8: |
|---|
| 271 | + case MEDIA_BUS_FMT_SBGGR10_1X10: |
|---|
| 272 | + case MEDIA_BUS_FMT_SGBRG10_1X10: |
|---|
| 273 | + case MEDIA_BUS_FMT_SGRBG10_1X10: |
|---|
| 274 | + case MEDIA_BUS_FMT_SRGGB10_1X10: |
|---|
| 275 | + case MEDIA_BUS_FMT_SBGGR12_1X12: |
|---|
| 276 | + case MEDIA_BUS_FMT_SGBRG12_1X12: |
|---|
| 277 | + case MEDIA_BUS_FMT_SGRBG12_1X12: |
|---|
| 278 | + case MEDIA_BUS_FMT_SRGGB12_1X12: |
|---|
| 279 | + case MEDIA_BUS_FMT_SBGGR14_1X14: |
|---|
| 280 | + case MEDIA_BUS_FMT_SGBRG14_1X14: |
|---|
| 281 | + case MEDIA_BUS_FMT_SGRBG14_1X14: |
|---|
| 282 | + case MEDIA_BUS_FMT_SRGGB14_1X14: |
|---|
| 283 | + case MEDIA_BUS_FMT_SBGGR16_1X16: |
|---|
| 284 | + case MEDIA_BUS_FMT_SGBRG16_1X16: |
|---|
| 285 | + case MEDIA_BUS_FMT_SGRBG16_1X16: |
|---|
| 286 | + case MEDIA_BUS_FMT_SRGGB16_1X16: |
|---|
| 264 | 287 | break; |
|---|
| 265 | 288 | default: |
|---|
| 266 | 289 | sdformat->format.code = MEDIA_BUS_FMT_Y8_1X8; |
|---|
| .. | .. |
|---|
| 316 | 339 | .video = &video_mux_subdev_video_ops, |
|---|
| 317 | 340 | }; |
|---|
| 318 | 341 | |
|---|
| 342 | +static int video_mux_notify_bound(struct v4l2_async_notifier *notifier, |
|---|
| 343 | + struct v4l2_subdev *sd, |
|---|
| 344 | + struct v4l2_async_subdev *asd) |
|---|
| 345 | +{ |
|---|
| 346 | + struct video_mux *vmux = notifier_to_video_mux(notifier); |
|---|
| 347 | + |
|---|
| 348 | + return v4l2_create_fwnode_links(sd, &vmux->subdev); |
|---|
| 349 | +} |
|---|
| 350 | + |
|---|
| 351 | +static const struct v4l2_async_notifier_operations video_mux_notify_ops = { |
|---|
| 352 | + .bound = video_mux_notify_bound, |
|---|
| 353 | +}; |
|---|
| 354 | + |
|---|
| 355 | +static int video_mux_async_register(struct video_mux *vmux, |
|---|
| 356 | + unsigned int num_input_pads) |
|---|
| 357 | +{ |
|---|
| 358 | + unsigned int i; |
|---|
| 359 | + int ret; |
|---|
| 360 | + |
|---|
| 361 | + v4l2_async_notifier_init(&vmux->notifier); |
|---|
| 362 | + |
|---|
| 363 | + for (i = 0; i < num_input_pads; i++) { |
|---|
| 364 | + struct v4l2_async_subdev *asd; |
|---|
| 365 | + struct fwnode_handle *ep, *remote_ep; |
|---|
| 366 | + |
|---|
| 367 | + ep = fwnode_graph_get_endpoint_by_id( |
|---|
| 368 | + dev_fwnode(vmux->subdev.dev), i, 0, |
|---|
| 369 | + FWNODE_GRAPH_ENDPOINT_NEXT); |
|---|
| 370 | + if (!ep) |
|---|
| 371 | + continue; |
|---|
| 372 | + |
|---|
| 373 | + /* Skip dangling endpoints for backwards compatibility */ |
|---|
| 374 | + remote_ep = fwnode_graph_get_remote_endpoint(ep); |
|---|
| 375 | + if (!remote_ep) { |
|---|
| 376 | + fwnode_handle_put(ep); |
|---|
| 377 | + continue; |
|---|
| 378 | + } |
|---|
| 379 | + fwnode_handle_put(remote_ep); |
|---|
| 380 | + |
|---|
| 381 | + asd = v4l2_async_notifier_add_fwnode_remote_subdev( |
|---|
| 382 | + &vmux->notifier, ep, sizeof(*asd)); |
|---|
| 383 | + |
|---|
| 384 | + fwnode_handle_put(ep); |
|---|
| 385 | + |
|---|
| 386 | + if (IS_ERR(asd)) { |
|---|
| 387 | + ret = PTR_ERR(asd); |
|---|
| 388 | + /* OK if asd already exists */ |
|---|
| 389 | + if (ret != -EEXIST) |
|---|
| 390 | + return ret; |
|---|
| 391 | + } |
|---|
| 392 | + } |
|---|
| 393 | + |
|---|
| 394 | + vmux->notifier.ops = &video_mux_notify_ops; |
|---|
| 395 | + |
|---|
| 396 | + ret = v4l2_async_subdev_notifier_register(&vmux->subdev, |
|---|
| 397 | + &vmux->notifier); |
|---|
| 398 | + if (ret) |
|---|
| 399 | + return ret; |
|---|
| 400 | + |
|---|
| 401 | + return v4l2_async_register_subdev(&vmux->subdev); |
|---|
| 402 | +} |
|---|
| 403 | + |
|---|
| 319 | 404 | static int video_mux_probe(struct platform_device *pdev) |
|---|
| 320 | 405 | { |
|---|
| 321 | 406 | struct device_node *np = pdev->dev.of_node; |
|---|
| .. | .. |
|---|
| 333 | 418 | platform_set_drvdata(pdev, vmux); |
|---|
| 334 | 419 | |
|---|
| 335 | 420 | v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops); |
|---|
| 336 | | - snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%s", np->name); |
|---|
| 421 | + snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%pOFn", np); |
|---|
| 337 | 422 | vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
|---|
| 338 | 423 | vmux->subdev.dev = dev; |
|---|
| 339 | 424 | |
|---|
| .. | .. |
|---|
| 388 | 473 | |
|---|
| 389 | 474 | vmux->subdev.entity.ops = &video_mux_ops; |
|---|
| 390 | 475 | |
|---|
| 391 | | - return v4l2_async_register_subdev(&vmux->subdev); |
|---|
| 476 | + ret = video_mux_async_register(vmux, num_pads - 1); |
|---|
| 477 | + if (ret) { |
|---|
| 478 | + v4l2_async_notifier_unregister(&vmux->notifier); |
|---|
| 479 | + v4l2_async_notifier_cleanup(&vmux->notifier); |
|---|
| 480 | + } |
|---|
| 481 | + |
|---|
| 482 | + return ret; |
|---|
| 392 | 483 | } |
|---|
| 393 | 484 | |
|---|
| 394 | 485 | static int video_mux_remove(struct platform_device *pdev) |
|---|
| .. | .. |
|---|
| 396 | 487 | struct video_mux *vmux = platform_get_drvdata(pdev); |
|---|
| 397 | 488 | struct v4l2_subdev *sd = &vmux->subdev; |
|---|
| 398 | 489 | |
|---|
| 490 | + v4l2_async_notifier_unregister(&vmux->notifier); |
|---|
| 491 | + v4l2_async_notifier_cleanup(&vmux->notifier); |
|---|
| 399 | 492 | v4l2_async_unregister_subdev(sd); |
|---|
| 400 | 493 | media_entity_cleanup(&sd->entity); |
|---|
| 401 | 494 | |
|---|