diff mbox series

[master/kirkstone,v2,2/2] v4l-utils: Add multistream and RGB-IR support

Message ID 20230525104036.2050245-2-j-luthra@ti.com
State Accepted
Delegated to: Ryan Eatmon
Headers show
Series [master/kirkstone,v2,1/2] v4l-utils: Add recipe for v1.24.1 | expand

Commit Message

Jai Luthra May 25, 2023, 10:40 a.m. UTC
Apply patches for routing and multistream APIs, and the new 4x4 RGB-IR
bayer format used by the OV2312 camera sensor in TI's 6.1 linux kernel.

Signed-off-by: Jai Luthra <j-luthra@ti.com>
---

No change in v2

 ...-add-support-for-RGBIr-bayer-formats.patch |   62 +
 ...-ctl-Add-routing-and-streams-support.patch |  619 ++++++++++
 ...l-add-support-for-routes-and-streams.patch | 1022 +++++++++++++++++
 ...nce-add-routing-and-streams-multiple.patch |  460 ++++++++
 .../v4l-utils/v4l-utils_1.24.1.bb             |    4 +
 5 files changed, 2167 insertions(+)
 create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
 create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
 create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
 create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch

Comments

Devarsh Thakkar May 25, 2023, 1:40 p.m. UTC | #1
Hi Jai,

Thanks for the patch.

On 25/05/23 16:10, Jai Luthra wrote:
> Apply patches for routing and multistream APIs, and the new 4x4 RGB-IR
> bayer format used by the OV2312 camera sensor in TI's 6.1 linux kernel.
> 
> Signed-off-by: Jai Luthra <j-luthra@ti.com>

Reviewed-by: Devarsh Thakkar <devarsht@ti.com>

Regards
Devarsh

> ---
> 
> No change in v2
> 
>  ...-add-support-for-RGBIr-bayer-formats.patch |   62 +
>  ...-ctl-Add-routing-and-streams-support.patch |  619 ++++++++++
>  ...l-add-support-for-routes-and-streams.patch | 1022 +++++++++++++++++
>  ...nce-add-routing-and-streams-multiple.patch |  460 ++++++++
>  .../v4l-utils/v4l-utils_1.24.1.bb             |    4 +
>  5 files changed, 2167 insertions(+)
>  create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
>  create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
>  create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
>  create mode 100644 meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch
> 
> diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
> new file mode 100644
> index 00000000..00f54456
> --- /dev/null
> +++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
> @@ -0,0 +1,62 @@
> +From 26e2a60d29456a9cc6acb16ea19039414808bc5e Mon Sep 17 00:00:00 2001
> +From: Jai Luthra <j-luthra@ti.com>
> +Date: Tue, 5 Jul 2022 16:23:39 +0530
> +Subject: [PATCH] media-ctl: add support for RGBIr bayer formats
> +
> +Signed-off-by: Jai Luthra <j-luthra@ti.com>
> +---
> + include/linux/media-bus-format.h | 10 +++++++++-
> + include/linux/videodev2.h        |  9 +++++++++
> + 2 files changed, 18 insertions(+), 1 deletion(-)
> +
> +diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h
> +index ca9a24c8..cbdf3798 100644
> +--- a/include/linux/media-bus-format.h
> ++++ b/include/linux/media-bus-format.h
> +@@ -117,7 +117,7 @@
> + #define MEDIA_BUS_FMT_YUV16_1X48		0x202a
> + #define MEDIA_BUS_FMT_UYYVYY16_0_5X48		0x202b
> + 
> +-/* Bayer - next is	0x3021 */
> ++/* Bayer - next is	0x3029 */
> + #define MEDIA_BUS_FMT_SBGGR8_1X8		0x3001
> + #define MEDIA_BUS_FMT_SGBRG8_1X8		0x3013
> + #define MEDIA_BUS_FMT_SGRBG8_1X8		0x3002
> +@@ -150,6 +150,14 @@
> + #define MEDIA_BUS_FMT_SGBRG16_1X16		0x301e
> + #define MEDIA_BUS_FMT_SGRBG16_1X16		0x301f
> + #define MEDIA_BUS_FMT_SRGGB16_1X16		0x3020
> ++#define MEDIA_BUS_FMT_SRGGI10_1X10		0x3021
> ++#define MEDIA_BUS_FMT_SGRIG10_1X10		0x3022
> ++#define MEDIA_BUS_FMT_SBGGI10_1X10		0x3023
> ++#define MEDIA_BUS_FMT_SGBIG10_1X10		0x3024
> ++#define MEDIA_BUS_FMT_SGIRG10_1X10		0x3025
> ++#define MEDIA_BUS_FMT_SIGGR10_1X10		0x3026
> ++#define MEDIA_BUS_FMT_SGIBG10_1X10		0x3027
> ++#define MEDIA_BUS_FMT_SIGGB10_1X10		0x3028
> + 
> + /* JPEG compressed formats - next is	0x4002 */
> + #define MEDIA_BUS_FMT_JPEG_1X8			0x4001
> +diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
> +index 5eb96692..093104ab 100644
> +--- a/include/linux/videodev2.h
> ++++ b/include/linux/videodev2.h
> +@@ -682,6 +682,15 @@ struct v4l2_pix_format {
> + #define V4L2_PIX_FMT_SGBRG16 v4l2_fourcc('G', 'B', '1', '6') /* 16  GBGB.. RGRG.. */
> + #define V4L2_PIX_FMT_SGRBG16 v4l2_fourcc('G', 'R', '1', '6') /* 16  GRGR.. BGBG.. */
> + #define V4L2_PIX_FMT_SRGGB16 v4l2_fourcc('R', 'G', '1', '6') /* 16  RGRG.. GBGB.. */
> ++	/* 10bit raw bayer with IR (4x4) */
> ++#define V4L2_PIX_FMT_SRGGI10 v4l2_fourcc('R', 'G', 'I', '0') /* 10 RGBG.. GIrGIr.. */
> ++#define V4L2_PIX_FMT_SGRIG10 v4l2_fourcc('G', 'R', 'I', '0') /* 10 GRGB.. IrGIrG.. */
> ++#define V4L2_PIX_FMT_SBGGI10 v4l2_fourcc('B', 'G', 'I', '0') /* 10 BGRG.. GIrGIr.. */
> ++#define V4L2_PIX_FMT_SGBIG10 v4l2_fourcc('G', 'B', 'I', '0') /* 10 GBGR.. IrGIrG.. */
> ++#define V4L2_PIX_FMT_SGIRG10 v4l2_fourcc('G', 'I', 'R', '0') /* 10 GIrGIr.. RGBG.. */
> ++#define V4L2_PIX_FMT_SIGGR10 v4l2_fourcc('I', 'G', 'R', '0') /* 10 IrGIrG.. GRGB.. */
> ++#define V4L2_PIX_FMT_SGIBG10 v4l2_fourcc('G', 'I', 'B', '0') /* 10 GIrGIr.. BGRG.. */
> ++#define V4L2_PIX_FMT_SIGGB10 v4l2_fourcc('I', 'G', 'B', '0') /* 10 IrGIrG.. GBGR.. */
> + 
> + /* HSV formats */
> + #define V4L2_PIX_FMT_HSV24 v4l2_fourcc('H', 'S', 'V', '3')
> +-- 
> +2.40.0
> +
> diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
> new file mode 100644
> index 00000000..90f15485
> --- /dev/null
> +++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
> @@ -0,0 +1,619 @@
> +From 3b57a10f899403acd877683ca0247f2a9eba8850 Mon Sep 17 00:00:00 2001
> +From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +Date: Fri, 10 Feb 2023 13:55:44 +0200
> +Subject: [PATCH 1/3] v4l2-ctl: Add routing and streams support
> +MIME-Version: 1.0
> +Content-Type: text/plain; charset=UTF-8
> +Content-Transfer-Encoding: 8bit
> +
> +Add support to get and set subdev routes and to get and set
> +configurations per stream.
> +
> +Based on work from Jacopo Mondi <jacopo@jmondi.org> and
> +Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>.
> +
> +Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +---
> + utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 288 +++++++++++++++++++++++++----
> + utils/v4l2-ctl/v4l2-ctl.cpp        |   2 +
> + utils/v4l2-ctl/v4l2-ctl.h          |   2 +
> + 3 files changed, 259 insertions(+), 33 deletions(-)
> +
> +diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> +index 33cc1342..81236451 100644
> +--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> ++++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> +@@ -1,5 +1,13 @@
> + #include "v4l2-ctl.h"
> + 
> ++#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
> ++
> ++/*
> ++ * The max value comes from a check in the kernel source code
> ++ * drivers/media/v4l2-core/v4l2-ioctl.c check_array_args()
> ++ */
> ++#define NUM_ROUTES_MAX 256
> ++
> + struct mbus_name {
> + 	const char *name;
> + 	__u32 code;
> +@@ -19,45 +27,57 @@ static const struct mbus_name mbus_names[] = {
> + #define SelectionFlags 		(1L<<4)
> + 
> + static __u32 list_mbus_codes_pad;
> ++static __u32 list_mbus_codes_stream = 0;
> + static __u32 get_fmt_pad;
> ++static __u32 get_fmt_stream = 0;
> + static __u32 get_sel_pad;
> ++static __u32 get_sel_stream = 0;
> + static __u32 get_fps_pad;
> ++static __u32 get_fps_stream = 0;
> + static int get_sel_target = -1;
> + static unsigned int set_selection;
> + static struct v4l2_subdev_selection vsel;
> + static unsigned int set_fmt;
> + static __u32 set_fmt_pad;
> ++static __u32 set_fmt_stream = 0;
> + static struct v4l2_mbus_framefmt ffmt;
> + static struct v4l2_subdev_frame_size_enum frmsize;
> + static struct v4l2_subdev_frame_interval_enum frmival;
> + static __u32 set_fps_pad;
> ++static __u32 set_fps_stream = 0;
> + static double set_fps;
> ++static struct v4l2_subdev_routing routing;
> ++static struct v4l2_subdev_route routes[NUM_ROUTES_MAX];
> + 
> + void subdev_usage()
> + {
> + 	printf("\nSub-Device options:\n"
> +-	       "  --list-subdev-mbus-codes <pad>\n"
> ++	       "  --list-subdev-mbus-codes pad=<pad>,stream=<stream>\n"
> + 	       "                      display supported mediabus codes for this pad (0 is default)\n"
> + 	       "                      [VIDIOC_SUBDEV_ENUM_MBUS_CODE]\n"
> +-	       "  --list-subdev-framesizes pad=<pad>,code=<code>\n"
> ++	       "  --list-subdev-framesizes pad=<pad>,stream=<stream>,code=<code>\n"
> + 	       "                     list supported framesizes for this pad and code\n"
> + 	       "                     [VIDIOC_SUBDEV_ENUM_FRAME_SIZE]\n"
> + 	       "                     <code> is the value of the mediabus code\n"
> +-	       "  --list-subdev-frameintervals pad=<pad>,width=<w>,height=<h>,code=<code>\n"
> ++	       "  --list-subdev-frameintervals pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>\n"
> + 	       "                     list supported frame intervals for this pad and code and\n"
> + 	       "                     the given width and height [VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL]\n"
> + 	       "                     <code> is the value of the mediabus code\n"
> +-	       "  --get-subdev-fmt [<pad>]\n"
> +-	       "     		     query the frame format for the given pad [VIDIOC_SUBDEV_G_FMT]\n"
> +-	       "  --get-subdev-selection pad=<pad>,target=<target>\n"
> ++	       "  --get-subdev-fmt pad=<pad>,stream=<stream>\n"
> ++	       "     		     query the frame format for the given pad and optional stream [VIDIOC_SUBDEV_G_FMT]\n"
> ++	       "		     <pad> the pad to get the format from\n"
> ++	       "		     <stream> the stream to get the format from (0 if not specified)\n"
> ++	       "  --get-subdev-selection pad=<pad>,stream=<stream>,target=<target>\n"
> + 	       "                     query the frame selection rectangle [VIDIOC_SUBDEV_G_SELECTION]\n"
> + 	       "                     See --set-subdev-selection command for the valid <target> values.\n"
> +-	       "  --get-subdev-fps [<pad>]\n"
> ++	       "  --get-subdev-fps pad=<pad>,stream=<stream>\n"
> + 	       "                     query the frame rate [VIDIOC_SUBDEV_G_FRAME_INTERVAL]\n"
> + 	       "  --set-subdev-fmt   (for testing only, otherwise use media-ctl)\n"
> +-	       "  --try-subdev-fmt pad=<pad>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
> ++	       "  --try-subdev-fmt pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
> + 	       "                   xfer=<xf>,ycbcr=<y>,hsv=<hsv>,quantization=<q>\n"
> +-	       "                     set the frame format [VIDIOC_SUBDEV_S_FMT]\n"
> ++	       "                     set the frame format for the given pad and optional stream [VIDIOC_SUBDEV_S_FMT]\n"
> ++	       "                     <pad> the pad to get the format from\n"
> ++	       "                     <stream> the stream to get the format from (0 if not specified)\n"
> + 	       "                     <code> is the value of the mediabus code\n"
> + 	       "                     <f> can be one of the following field layouts:\n"
> + 	       "                       any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
> +@@ -74,14 +94,30 @@ void subdev_usage()
> + 	       "                     <q> can be one of the following quantization methods:\n"
> + 	       "                       default, full-range, lim-range\n"
> + 	       "  --set-subdev-selection (for testing only, otherwise use media-ctl)\n"
> +-	       "  --try-subdev-selection pad=<pad>,target=<target>,flags=<flags>,\n"
> ++	       "  --try-subdev-selection pad=<pad>,stream=<stream>,target=<target>,flags=<flags>,\n"
> + 	       "                         top=<x>,left=<y>,width=<w>,height=<h>\n"
> + 	       "                     set the video capture selection rectangle [VIDIOC_SUBDEV_S_SELECTION]\n"
> + 	       "                     target=crop|crop_bounds|crop_default|compose|compose_bounds|\n"
> + 	       "                            compose_default|compose_padded|native_size\n"
> + 	       "                     flags=le|ge|keep-config\n"
> +-	       "  --set-subdev-fps pad=<pad>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
> ++	       "  --set-subdev-fps pad=<pad>,stream=<stream>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
> + 	       "                     set the frame rate [VIDIOC_SUBDEV_S_FRAME_INTERVAL]\n"
> ++	       "  --get-routing      Print the route topology\n"
> ++	       "  --set-routing <routes>\n"
> ++	       "                     Comma-separated list of route descriptors to setup\n"
> ++	       "\n"
> ++	       "Routes are defined as\n"
> ++	       "	routes		= route { ',' route } ;\n"
> ++	       "	route		= sink '->' source '[' flags ']' ;\n"
> ++	       "	sink		= sink-pad '/' sink-stream ;\n"
> ++	       "	source		= source-pad '/' source-stream ;\n"
> ++	       "\n"
> ++	       "where\n"
> ++	       "	sink-pad	= Pad numeric identifier for sink\n"
> ++	       "	sink-stream	= Stream numeric identifier for sink\n"
> ++	       "	source-pad	= Pad numeric identifier for source\n"
> ++	       "	source-stream	= Stream numeric identifier for source\n"
> ++	       "	flags		= Route flags (0: inactive, 1: active)\n"
> + 	       );
> + }
> + 
> +@@ -91,14 +127,33 @@ void subdev_cmd(int ch, char *optarg)
> + 
> + 	switch (ch) {
> + 	case OptListSubDevMBusCodes:
> +-		if (optarg)
> +-			list_mbus_codes_pad = strtoul(optarg, nullptr, 0);
> ++		subs = optarg;
> ++		while (subs && *subs != '\0') {
> ++			static constexpr const char *subopts[] = {
> ++				"pad",
> ++				"stream",
> ++				nullptr
> ++			};
> ++
> ++			switch (parse_subopt(&subs, subopts, &value)) {
> ++			case 0:
> ++				list_mbus_codes_pad = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 1:
> ++				list_mbus_codes_stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			default:
> ++				subdev_usage();
> ++				std::exit(EXIT_FAILURE);
> ++			}
> ++		}
> + 		break;
> + 	case OptListSubDevFrameSizes:
> + 		subs = optarg;
> + 		while (*subs != '\0') {
> + 			static constexpr const char *subopts[] = {
> + 				"pad",
> ++				"stream",
> + 				"code",
> + 				nullptr
> + 			};
> +@@ -108,6 +163,9 @@ void subdev_cmd(int ch, char *optarg)
> + 				frmsize.pad = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 1:
> ++				frmsize.stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 2:
> + 				frmsize.code = strtoul(value, nullptr, 0);
> + 				break;
> + 			default:
> +@@ -121,6 +179,7 @@ void subdev_cmd(int ch, char *optarg)
> + 		while (*subs != '\0') {
> + 			static constexpr const char *subopts[] = {
> + 				"pad",
> ++				"stream",
> + 				"code",
> + 				"width",
> + 				"height",
> +@@ -132,12 +191,15 @@ void subdev_cmd(int ch, char *optarg)
> + 				frmival.pad = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 1:
> +-				frmival.code = strtoul(value, nullptr, 0);
> ++				frmival.stream = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 2:
> +-				frmival.width = strtoul(value, nullptr, 0);
> ++				frmival.code = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 3:
> ++				frmival.width = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 4:
> + 				frmival.height = strtoul(value, nullptr, 0);
> + 				break;
> + 			default:
> +@@ -147,14 +209,33 @@ void subdev_cmd(int ch, char *optarg)
> + 		}
> + 		break;
> + 	case OptGetSubDevFormat:
> +-		if (optarg)
> +-			get_fmt_pad = strtoul(optarg, nullptr, 0);
> ++		subs = optarg;
> ++		while (subs && *subs != '\0') {
> ++			static constexpr const char *subopts[] = {
> ++				"pad",
> ++				"stream",
> ++				nullptr
> ++			};
> ++
> ++			switch (parse_subopt(&subs, subopts, &value)) {
> ++			case 0:
> ++				get_fmt_pad = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 1:
> ++				get_fmt_stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			default:
> ++				subdev_usage();
> ++				std::exit(EXIT_FAILURE);
> ++			}
> ++		}
> + 		break;
> + 	case OptGetSubDevSelection:
> + 		subs = optarg;
> + 		while (*subs != '\0') {
> + 			static constexpr const char *subopts[] = {
> + 				"pad",
> ++				"stream",
> + 				"target",
> + 				nullptr
> + 			};
> +@@ -165,6 +246,9 @@ void subdev_cmd(int ch, char *optarg)
> + 				get_sel_pad = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 1:
> ++				get_sel_stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 2:
> + 				if (parse_selection_target(value, target)) {
> + 					fprintf(stderr, "Unknown selection target\n");
> + 					subdev_usage();
> +@@ -179,8 +263,26 @@ void subdev_cmd(int ch, char *optarg)
> + 		}
> + 		break;
> + 	case OptGetSubDevFPS:
> +-		if (optarg)
> +-			get_fps_pad = strtoul(optarg, nullptr, 0);
> ++		subs = optarg;
> ++		while (subs && *subs != '\0') {
> ++			static constexpr const char *subopts[] = {
> ++				"pad",
> ++				"stream",
> ++				nullptr
> ++			};
> ++
> ++			switch (parse_subopt(&subs, subopts, &value)) {
> ++			case 0:
> ++				get_fps_pad = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 1:
> ++				get_fps_stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			default:
> ++				subdev_usage();
> ++				std::exit(EXIT_FAILURE);
> ++			}
> ++		}
> + 		break;
> + 	case OptSetSubDevFormat:
> + 	case OptTrySubDevFormat:
> +@@ -198,6 +300,7 @@ void subdev_cmd(int ch, char *optarg)
> + 				"quantization",
> + 				"xfer",
> + 				"pad",
> ++				"stream",
> + 				nullptr
> + 			};
> + 
> +@@ -244,6 +347,9 @@ void subdev_cmd(int ch, char *optarg)
> + 			case 9:
> + 				set_fmt_pad = strtoul(value, nullptr, 0);
> + 				break;
> ++			case 10:
> ++				set_fmt_stream = strtoul(value, nullptr, 0);
> ++				break;
> + 			default:
> + 				fprintf(stderr, "Unknown option\n");
> + 				subdev_usage();
> +@@ -264,6 +370,7 @@ void subdev_cmd(int ch, char *optarg)
> + 				"width",
> + 				"height",
> + 				"pad",
> ++				"stream",
> + 				nullptr
> + 			};
> + 
> +@@ -298,6 +405,9 @@ void subdev_cmd(int ch, char *optarg)
> + 			case 6:
> + 				vsel.pad = strtoul(value, nullptr, 0);
> + 				break;
> ++			case 7:
> ++				vsel.stream = strtoul(value, nullptr, 0);
> ++				break;
> + 			default:
> + 				fprintf(stderr, "Unknown option\n");
> + 				subdev_usage();
> +@@ -311,6 +421,7 @@ void subdev_cmd(int ch, char *optarg)
> + 		while (*subs != '\0') {
> + 			static constexpr const char *subopts[] = {
> + 				"pad",
> ++				"stream",
> + 				"fps",
> + 				nullptr
> + 			};
> +@@ -320,6 +431,9 @@ void subdev_cmd(int ch, char *optarg)
> + 				set_fps_pad = strtoul(value, nullptr, 0);
> + 				break;
> + 			case 1:
> ++				set_fps_stream = strtoul(value, nullptr, 0);
> ++				break;
> ++			case 2:
> + 				set_fps = strtod(value, nullptr);
> + 				break;
> + 			default:
> +@@ -329,6 +443,47 @@ void subdev_cmd(int ch, char *optarg)
> + 			}
> + 		}
> + 		break;
> ++	case OptSetRouting: {
> ++		struct v4l2_subdev_route *r;
> ++		char *end, *ref, *tok;
> ++		unsigned int flags;
> ++
> ++		memset(&routing, 0, sizeof(routing));
> ++		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
> ++		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> ++		routing.num_routes = 0;
> ++		routing.routes = (__u64)routes;
> ++
> ++		if (!optarg)
> ++			break;
> ++
> ++		r = (v4l2_subdev_route *)routing.routes;
> ++		ref = end = strdup(optarg);
> ++		while ((tok = strsep(&end, ",")) != NULL) {
> ++			if (sscanf(tok, "%u/%u -> %u/%u [%u]",
> ++				   &r->sink_pad, &r->sink_stream,
> ++				   &r->source_pad, &r->source_stream,
> ++				   &flags) != 5) {
> ++				free(ref);
> ++				fprintf(stderr, "Invalid route information specified\n");
> ++				subdev_usage();
> ++				std::exit(EXIT_FAILURE);
> ++			}
> ++
> ++			if (flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
> ++				fprintf(stderr, "Invalid route flags specified: %#x\n", flags);
> ++				subdev_usage();
> ++				std::exit(EXIT_FAILURE);
> ++			}
> ++
> ++			r->flags = flags;
> ++
> ++			r++;
> ++			routing.num_routes++;
> ++		}
> ++		free(ref);
> ++		break;
> ++	}
> + 	default:
> + 		break;
> + 	}
> +@@ -394,6 +549,7 @@ void subdev_set(cv4l_fd &_fd)
> + 
> + 		memset(&fmt, 0, sizeof(fmt));
> + 		fmt.pad = set_fmt_pad;
> ++		fmt.stream = set_fmt_stream;
> + 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + 
> + 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0) {
> +@@ -430,7 +586,7 @@ void subdev_set(cv4l_fd &_fd)
> + 			else
> + 				fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> + 
> +-			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u)\n", fmt.pad);
> ++			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u,stream=%u)\n", fmt.pad, fmt.stream);
> + 			ret = doioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt);
> + 			if (ret == 0 && (verbose || !options[OptSetSubDevFormat]))
> + 				print_framefmt(fmt.format);
> +@@ -441,6 +597,7 @@ void subdev_set(cv4l_fd &_fd)
> + 
> + 		memset(&sel, 0, sizeof(sel));
> + 		sel.pad = vsel.pad;
> ++		sel.stream = vsel.stream;
> + 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + 		sel.target = vsel.target;
> + 
> +@@ -461,7 +618,7 @@ void subdev_set(cv4l_fd &_fd)
> + 			else
> + 				sel.which = V4L2_SUBDEV_FORMAT_TRY;
> + 
> +-			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u)\n", sel.pad);
> ++			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
> + 			int ret = doioctl(fd, VIDIOC_SUBDEV_S_SELECTION, &sel);
> + 			if (ret == 0 && (verbose || !options[OptSetSubDevSelection]))
> + 				print_subdev_selection(sel);
> +@@ -472,6 +629,7 @@ void subdev_set(cv4l_fd &_fd)
> + 
> + 		memset(&fival, 0, sizeof(fival));
> + 		fival.pad = set_fps_pad;
> ++		fival.stream = set_fps_stream;
> + 
> + 		if (set_fps <= 0) {
> + 			fprintf(stderr, "invalid fps %f\n", set_fps);
> +@@ -482,7 +640,7 @@ void subdev_set(cv4l_fd &_fd)
> + 		fival.interval.denominator = static_cast<uint32_t>(set_fps * fival.interval.numerator);
> + 		printf("Note: --set-subdev-fps is only for testing.\n"
> + 		       "Normally media-ctl is used to configure the video pipeline.\n");
> +-		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u)\n", fival.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
> + 		if (doioctl(fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) == 0) {
> + 			if (!fival.interval.denominator || !fival.interval.numerator)
> + 				printf("\tFrames per second: invalid (%d/%d)\n",
> +@@ -493,6 +651,55 @@ void subdev_set(cv4l_fd &_fd)
> + 					fival.interval.denominator, fival.interval.numerator);
> + 		}
> + 	}
> ++	if (options[OptSetRouting]) {
> ++		if (doioctl(fd, VIDIOC_SUBDEV_S_ROUTING, &routing) == 0)
> ++			printf("Routing set\n");
> ++	}
> ++}
> ++
> ++struct flag_name {
> ++	__u32 flag;
> ++	const char *name;
> ++};
> ++
> ++static void print_flags(const struct flag_name *flag_names, unsigned int num_entries, __u32 flags)
> ++{
> ++	bool first = true;
> ++	unsigned int i;
> ++
> ++	for (i = 0; i < num_entries; i++) {
> ++		if (!(flags & flag_names[i].flag))
> ++			continue;
> ++		if (!first)
> ++			printf(",");
> ++		printf("%s", flag_names[i].name);
> ++		flags &= ~flag_names[i].flag;
> ++		first = false;
> ++	}
> ++
> ++	if (flags) {
> ++		if (!first)
> ++			printf(",");
> ++		printf("0x%x", flags);
> ++	}
> ++}
> ++
> ++static void print_routes(const struct v4l2_subdev_routing *r)
> ++{
> ++	unsigned int i;
> ++	struct v4l2_subdev_route *routes = (struct v4l2_subdev_route *)r->routes;
> ++
> ++	static const struct flag_name route_flags[] = {
> ++		{ V4L2_SUBDEV_ROUTE_FL_ACTIVE, "ACTIVE" },
> ++	};
> ++
> ++	for (i = 0; i < r->num_routes; i++) {
> ++		printf("%d/%d -> %d/%d [",
> ++		       routes[i].sink_pad, routes[i].sink_stream,
> ++		       routes[i].source_pad, routes[i].source_stream);
> ++		print_flags(route_flags, ARRAY_SIZE(route_flags), routes[i].flags);
> ++		printf("]\n");
> ++	}
> + }
> + 
> + void subdev_get(cv4l_fd &_fd)
> +@@ -505,8 +712,9 @@ void subdev_get(cv4l_fd &_fd)
> + 		memset(&fmt, 0, sizeof(fmt));
> + 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + 		fmt.pad = get_fmt_pad;
> ++		fmt.stream = get_fmt_stream;
> + 
> +-		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u)\n", fmt.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u, stream=%u)\n", fmt.pad, fmt.stream);
> + 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0)
> + 			print_framefmt(fmt.format);
> + 	}
> +@@ -518,8 +726,9 @@ void subdev_get(cv4l_fd &_fd)
> + 		memset(&sel, 0, sizeof(sel));
> + 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + 		sel.pad = get_sel_pad;
> ++		sel.stream = get_sel_stream;
> + 
> +-		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u)\n", sel.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
> + 		if (options[OptAll] || get_sel_target == -1) {
> + 			while (valid_seltarget_at_idx(idx)) {
> + 				sel.target = seltarget_at_idx(idx);
> +@@ -538,8 +747,9 @@ void subdev_get(cv4l_fd &_fd)
> + 
> + 		memset(&fival, 0, sizeof(fival));
> + 		fival.pad = get_fps_pad;
> ++		fival.stream = get_fps_stream;
> + 
> +-		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u)\n", fival.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
> + 		if (doioctl(fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) == 0) {
> + 			if (!fival.interval.denominator || !fival.interval.numerator)
> + 				printf("\tFrames per second: invalid (%d/%d)\n",
> +@@ -550,6 +760,17 @@ void subdev_get(cv4l_fd &_fd)
> + 					fival.interval.denominator, fival.interval.numerator);
> + 		}
> + 	}
> ++
> ++	if (options[OptGetRouting]) {
> ++		memset(&routing, 0, sizeof(routing));
> ++		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
> ++		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> ++		routing.num_routes = NUM_ROUTES_MAX;
> ++		routing.routes = (__u64)routes;
> ++
> ++		if (doioctl(fd, VIDIOC_SUBDEV_G_ROUTING, &routing) == 0)
> ++			print_routes(&routing);
> ++	}
> + }
> + 
> + static void print_mbus_code(__u32 code)
> +@@ -566,11 +787,12 @@ static void print_mbus_code(__u32 code)
> + 		printf("\t0x%04x", code);
> + }
> + 
> +-static void print_mbus_codes(int fd, __u32 pad)
> ++static void print_mbus_codes(int fd, __u32 pad, __u32 stream)
> + {
> + 	struct v4l2_subdev_mbus_code_enum mbus_code = {};
> + 
> + 	mbus_code.pad = pad;
> ++	mbus_code.stream = stream;
> + 	mbus_code.which = V4L2_SUBDEV_FORMAT_TRY;
> + 
> + 	for (;;) {
> +@@ -623,13 +845,13 @@ void subdev_list(cv4l_fd &_fd)
> + 	int fd = _fd.g_fd();
> + 
> + 	if (options[OptListSubDevMBusCodes]) {
> +-		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u)\n",
> +-		       list_mbus_codes_pad);
> +-		print_mbus_codes(fd, list_mbus_codes_pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u,stream=%u)\n",
> ++		       list_mbus_codes_pad, list_mbus_codes_stream);
> ++		print_mbus_codes(fd, list_mbus_codes_pad, list_mbus_codes_stream);
> + 	}
> + 	if (options[OptListSubDevFrameSizes]) {
> +-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u)\n",
> +-		       frmsize.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u,stream=%u)\n",
> ++		       frmsize.pad, frmsize.stream);
> + 		frmsize.index = 0;
> + 		frmsize.which = V4L2_SUBDEV_FORMAT_TRY;
> + 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &frmsize) >= 0) {
> +@@ -638,8 +860,8 @@ void subdev_list(cv4l_fd &_fd)
> + 		}
> + 	}
> + 	if (options[OptListSubDevFrameIntervals]) {
> +-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u)\n",
> +-		       frmival.pad);
> ++		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u,stream=%u)\n",
> ++		       frmival.pad, frmival.stream);
> + 		frmival.index = 0;
> + 		frmival.which = V4L2_SUBDEV_FORMAT_TRY;
> + 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &frmival) >= 0) {
> +diff --git a/utils/v4l2-ctl/v4l2-ctl.cpp b/utils/v4l2-ctl/v4l2-ctl.cpp
> +index 8585278f..1cfb50f7 100644
> +--- a/utils/v4l2-ctl/v4l2-ctl.cpp
> ++++ b/utils/v4l2-ctl/v4l2-ctl.cpp
> +@@ -64,6 +64,8 @@ static struct option long_options[] = {
> + 	{"get-fmt-video-out", no_argument, nullptr, OptGetVideoOutFormat},
> + 	{"set-fmt-video-out", required_argument, nullptr, OptSetVideoOutFormat},
> + 	{"try-fmt-video-out", required_argument, nullptr, OptTryVideoOutFormat},
> ++	{"set-routing", required_argument, 0, OptSetRouting},
> ++	{"get-routing", no_argument, 0, OptGetRouting},
> + 	{"help", no_argument, nullptr, OptHelp},
> + 	{"help-tuner", no_argument, nullptr, OptHelpTuner},
> + 	{"help-io", no_argument, nullptr, OptHelpIO},
> +diff --git a/utils/v4l2-ctl/v4l2-ctl.h b/utils/v4l2-ctl/v4l2-ctl.h
> +index 70a80ade..51a68b92 100644
> +--- a/utils/v4l2-ctl/v4l2-ctl.h
> ++++ b/utils/v4l2-ctl/v4l2-ctl.h
> +@@ -197,6 +197,8 @@ enum Option {
> + 	OptInfoEdid,
> + 	OptShowEdid,
> + 	OptFixEdidChecksums,
> ++	OptSetRouting,
> ++	OptGetRouting,
> + 	OptFreqSeek,
> + 	OptEncoderCmd,
> + 	OptTryEncoderCmd,
> +-- 
> +2.40.0
> +
> diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
> new file mode 100644
> index 00000000..b2d4eded
> --- /dev/null
> +++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
> @@ -0,0 +1,1022 @@
> +From 868c176e0de433777d5eed3e6d6d8dc03b9145a6 Mon Sep 17 00:00:00 2001
> +From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +Date: Fri, 10 Feb 2023 13:55:45 +0200
> +Subject: [PATCH 2/3] media-ctl: add support for routes and streams
> +
> +Add support to get and set subdev routes and to get and set
> +configurations per stream.
> +
> +Based on work from Sakari Ailus <sakari.ailus@linux.intel.com>.
> +
> +Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +---
> + utils/media-ctl/libmediactl.c   |  41 +++++
> + utils/media-ctl/libv4l2subdev.c | 283 ++++++++++++++++++++++++++++----
> + utils/media-ctl/media-ctl.c     | 121 ++++++++++++--
> + utils/media-ctl/mediactl.h      |  16 ++
> + utils/media-ctl/options.c       |  15 +-
> + utils/media-ctl/options.h       |   1 +
> + utils/media-ctl/v4l2subdev.h    |  58 ++++++-
> + 7 files changed, 478 insertions(+), 57 deletions(-)
> +
> +diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
> +index 1fd6525b..537365d0 100644
> +--- a/utils/media-ctl/libmediactl.c
> ++++ b/utils/media-ctl/libmediactl.c
> +@@ -876,6 +876,47 @@ struct media_pad *media_parse_pad(struct media_device *media,
> + 	return &entity->pads[pad];
> + }
> + 
> ++struct media_pad *media_parse_pad_stream(struct media_device *media,
> ++					 const char *p, unsigned int *stream,
> ++					 char **endp)
> ++{
> ++	struct media_pad *pad;
> ++	const char *orig_p = p;
> ++	char *ep;
> ++
> ++	pad = media_parse_pad(media, p, &ep);
> ++	if (pad == NULL)
> ++		return NULL;
> ++
> ++	p = ep;
> ++
> ++	if (*p == '/') {
> ++		unsigned int s;
> ++
> ++		p++;
> ++
> ++		s = strtoul(p, &ep, 10);
> ++
> ++		if (ep == p) {
> ++			printf("Unable to parse stream: '%s'\n", orig_p);
> ++			if (endp)
> ++				*endp = (char*)p;
> ++			return NULL;
> ++		}
> ++
> ++		*stream = s;
> ++
> ++		p++;
> ++	} else {
> ++		*stream = 0;
> ++	}
> ++
> ++	if (endp)
> ++		*endp = (char*)p;
> ++
> ++	return pad;
> ++}
> ++
> + struct media_link *media_parse_link(struct media_device *media,
> + 				    const char *p, char **endp)
> + {
> +diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
> +index 63bb3d75..d203e5b4 100644
> +--- a/utils/media-ctl/libv4l2subdev.c
> ++++ b/utils/media-ctl/libv4l2subdev.c
> +@@ -64,7 +64,7 @@ void v4l2_subdev_close(struct media_entity *entity)
> + }
> + 
> + int v4l2_subdev_get_format(struct media_entity *entity,
> +-	struct v4l2_mbus_framefmt *format, unsigned int pad,
> ++	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
> + 	enum v4l2_subdev_format_whence which)
> + {
> + 	struct v4l2_subdev_format fmt;
> +@@ -76,6 +76,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
> + 
> + 	memset(&fmt, 0, sizeof(fmt));
> + 	fmt.pad = pad;
> ++	fmt.stream = stream;
> + 	fmt.which = which;
> + 
> + 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FMT, &fmt);
> +@@ -88,6 +89,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
> + 
> + int v4l2_subdev_set_format(struct media_entity *entity,
> + 	struct v4l2_mbus_framefmt *format, unsigned int pad,
> ++	unsigned int stream,
> + 	enum v4l2_subdev_format_whence which)
> + {
> + 	struct v4l2_subdev_format fmt;
> +@@ -99,6 +101,7 @@ int v4l2_subdev_set_format(struct media_entity *entity,
> + 
> + 	memset(&fmt, 0, sizeof(fmt));
> + 	fmt.pad = pad;
> ++	fmt.stream = stream;
> + 	fmt.which = which;
> + 	fmt.format = *format;
> + 
> +@@ -111,8 +114,8 @@ int v4l2_subdev_set_format(struct media_entity *entity,
> + }
> + 
> + int v4l2_subdev_get_selection(struct media_entity *entity,
> +-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
> +-	enum v4l2_subdev_format_whence which)
> ++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
> ++	unsigned int target, enum v4l2_subdev_format_whence which)
> + {
> + 	union {
> + 		struct v4l2_subdev_selection sel;
> +@@ -150,8 +153,8 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
> + }
> + 
> + int v4l2_subdev_set_selection(struct media_entity *entity,
> +-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
> +-	enum v4l2_subdev_format_whence which)
> ++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
> ++	unsigned int target, enum v4l2_subdev_format_whence which)
> + {
> + 	union {
> + 		struct v4l2_subdev_selection sel;
> +@@ -165,6 +168,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
> + 
> + 	memset(&u.sel, 0, sizeof(u.sel));
> + 	u.sel.pad = pad;
> ++	u.sel.stream = stream;
> + 	u.sel.target = target;
> + 	u.sel.which = which;
> + 	u.sel.r = *rect;
> +@@ -179,6 +183,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
> + 
> + 	memset(&u.crop, 0, sizeof(u.crop));
> + 	u.crop.pad = pad;
> ++	u.crop.stream = stream;
> + 	u.crop.which = which;
> + 	u.crop.rect = *rect;
> + 
> +@@ -190,6 +195,69 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
> + 	return 0;
> + }
> + 
> ++int v4l2_subdev_set_routing(struct media_entity *entity,
> ++			    struct v4l2_subdev_route *routes,
> ++			    unsigned int num_routes)
> ++{
> ++	struct v4l2_subdev_routing routing = {
> ++		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
> ++		.routes = (uintptr_t)routes,
> ++		.num_routes = num_routes,
> ++	};
> ++	int ret;
> ++
> ++	ret = v4l2_subdev_open(entity);
> ++	if (ret < 0)
> ++		return ret;
> ++
> ++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
> ++	if (ret == -1)
> ++		return -errno;
> ++
> ++	return 0;
> ++}
> ++
> ++int v4l2_subdev_get_routing(struct media_entity *entity,
> ++			    struct v4l2_subdev_route **routes,
> ++			    unsigned int *num_routes)
> ++{
> ++	struct v4l2_subdev_routing routing = { 0 };
> ++	struct v4l2_subdev_route *r;
> ++	int ret;
> ++
> ++	ret = v4l2_subdev_open(entity);
> ++	if (ret < 0)
> ++		return ret;
> ++
> ++	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> ++
> ++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
> ++	if (ret == -1 && errno != ENOSPC)
> ++		return -errno;
> ++
> ++	if (!routing.num_routes) {
> ++		*routes = NULL;
> ++		*num_routes = 0;
> ++		return 0;
> ++	}
> ++
> ++	r = calloc(routing.num_routes, sizeof(*r));
> ++	if (!r)
> ++		return -ENOMEM;
> ++
> ++	routing.routes = (uintptr_t)r;
> ++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
> ++	if (ret) {
> ++		free(r);
> ++		return ret;
> ++	}
> ++
> ++	*num_routes = routing.num_routes;
> ++	*routes = r;
> ++
> ++	return 0;
> ++}
> ++
> + int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
> + 	struct v4l2_dv_timings_cap *caps)
> + {
> +@@ -264,7 +332,7 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
> + 
> + int v4l2_subdev_get_frame_interval(struct media_entity *entity,
> + 				   struct v4l2_fract *interval,
> +-				   unsigned int pad)
> ++				   unsigned int pad, unsigned int stream)
> + {
> + 	struct v4l2_subdev_frame_interval ival;
> + 	int ret;
> +@@ -275,6 +343,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
> + 
> + 	memset(&ival, 0, sizeof(ival));
> + 	ival.pad = pad;
> ++	ival.stream = stream;
> + 
> + 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &ival);
> + 	if (ret < 0)
> +@@ -286,7 +355,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
> + 
> + int v4l2_subdev_set_frame_interval(struct media_entity *entity,
> + 				   struct v4l2_fract *interval,
> +-				   unsigned int pad)
> ++				   unsigned int pad, unsigned int stream)
> + {
> + 	struct v4l2_subdev_frame_interval ival;
> + 	int ret;
> +@@ -297,6 +366,7 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
> + 
> + 	memset(&ival, 0, sizeof(ival));
> + 	ival.pad = pad;
> ++	ival.stream = stream;
> + 	ival.interval = *interval;
> + 
> + 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &ival);
> +@@ -307,6 +377,155 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
> + 	return 0;
> + }
> + 
> ++static int v4l2_subdev_parse_setup_route(struct media_device *media,
> ++					 struct v4l2_subdev_route *r,
> ++					 const char *p, char **endp)
> ++{
> ++	char *end;
> ++
> ++	/* sink pad/stream */
> ++
> ++	r->sink_pad = strtoul(p, &end, 10);
> ++
> ++	if (*end != '/') {
> ++		media_dbg(media, "Expected '/'\n");
> ++		return -EINVAL;
> ++	}
> ++
> ++	p = end + 1;
> ++
> ++	r->sink_stream = strtoul(p, &end, 10);
> ++
> ++	for (; isspace(*end); ++end);
> ++
> ++	if (end[0] != '-' || end[1] != '>') {
> ++		media_dbg(media, "Expected '->'\n");
> ++		return -EINVAL;
> ++	}
> ++	p = end + 2;
> ++
> ++	/* source pad/stream */
> ++
> ++	r->source_pad = strtoul(p, &end, 10);
> ++
> ++	if (*end != '/') {
> ++		media_dbg(media, "Expected '/'\n");
> ++		return -EINVAL;
> ++	}
> ++
> ++	p = end + 1;
> ++
> ++	r->source_stream = strtoul(p, &end, 10);
> ++
> ++	/* flags */
> ++
> ++	for (; isspace(*end); ++end);
> ++
> ++	if (*end != '[') {
> ++		media_dbg(media, "Expected '['\n");
> ++		return -EINVAL;
> ++	}
> ++
> ++	for (end++; isspace(*end); ++end);
> ++
> ++	p = end;
> ++
> ++	r->flags = strtoul(p, &end, 0);
> ++
> ++	if (r->flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
> ++		media_dbg(media, "Bad route flags %#x\n", r->flags);
> ++		return -EINVAL;
> ++	}
> ++
> ++	for (; isspace(*end); ++end);
> ++
> ++	if (*end != ']') {
> ++		media_dbg(media, "Expected ']'\n");
> ++		return -EINVAL;
> ++	}
> ++	end++;
> ++
> ++	*endp = end;
> ++
> ++	return 0;
> ++}
> ++
> ++int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
> ++{
> ++	struct media_entity *entity;
> ++	struct v4l2_subdev_route *routes;
> ++	unsigned int num_routes;
> ++	char *end;
> ++	int ret;
> ++	int i;
> ++
> ++	entity = media_parse_entity(media, p, &end);
> ++	if (!entity)
> ++		return -EINVAL;
> ++
> ++	p = end;
> ++
> ++	if (*p != '[') {
> ++		media_dbg(media, "Expected '['\n");
> ++		return -EINVAL;
> ++	}
> ++
> ++	p++;
> ++
> ++	routes = calloc(256, sizeof(routes[0]));
> ++	if (!routes)
> ++		return -ENOMEM;
> ++
> ++	num_routes = 0;
> ++
> ++	while (*p != 0) {
> ++		struct v4l2_subdev_route *r = &routes[num_routes];
> ++
> ++		ret = v4l2_subdev_parse_setup_route(media, r, p, &end);
> ++		if (ret)
> ++			goto out;
> ++
> ++		p = end;
> ++
> ++		num_routes++;
> ++
> ++		if (*p == ',') {
> ++			p++;
> ++			continue;
> ++		}
> ++
> ++		break;
> ++	}
> ++
> ++	if (*p != ']') {
> ++		media_dbg(media, "Expected ']'\n");
> ++		ret = -EINVAL;
> ++		goto out;
> ++	}
> ++
> ++	for (i = 0; i < num_routes; ++i) {
> ++		struct v4l2_subdev_route *r = &routes[i];
> ++
> ++		media_dbg(entity->media,
> ++			  "Setting up route %s : %u/%u -> %u/%u, flags 0x%8.8x\n",
> ++			  entity->info.name,
> ++			  r->sink_pad, r->sink_stream,
> ++			  r->source_pad, r->source_stream,
> ++			  r->flags);
> ++	}
> ++
> ++	ret = v4l2_subdev_set_routing(entity, routes, num_routes);
> ++	if (ret) {
> ++		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
> ++		goto out;
> ++	}
> ++
> ++out:
> ++	free(routes);
> ++
> ++	return ret;
> ++}
> ++
> + static int v4l2_subdev_parse_format(struct media_device *media,
> + 				    struct v4l2_mbus_framefmt *format,
> + 				    const char *p, char **endp)
> +@@ -442,7 +661,8 @@ static bool strhazit(const char *str, const char **p)
> + }
> + 
> + static struct media_pad *v4l2_subdev_parse_pad_format(
> +-	struct media_device *media, struct v4l2_mbus_framefmt *format,
> ++	struct media_device *media, unsigned int *stream,
> ++	struct v4l2_mbus_framefmt *format,
> + 	struct v4l2_rect *crop, struct v4l2_rect *compose,
> + 	struct v4l2_fract *interval, const char *p, char **endp)
> + {
> +@@ -453,7 +673,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
> + 
> + 	for (; isspace(*p); ++p);
> + 
> +-	pad = media_parse_pad(media, p, &end);
> ++	pad = media_parse_pad_stream(media, p, stream, &end);
> + 	if (pad == NULL) {
> + 		*endp = end;
> + 		return NULL;
> +@@ -675,6 +895,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
> + }
> + 
> + static int set_format(struct media_pad *pad,
> ++		      unsigned int stream,
> + 		      struct v4l2_mbus_framefmt *format)
> + {
> + 	int ret;
> +@@ -683,12 +904,12 @@ static int set_format(struct media_pad *pad,
> + 		return 0;
> + 
> + 	media_dbg(pad->entity->media,
> +-		  "Setting up format %s %ux%u on pad %s/%u\n",
> ++		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
> + 		  v4l2_subdev_pixelcode_to_string(format->code),
> + 		  format->width, format->height,
> +-		  pad->entity->info.name, pad->index);
> ++		  pad->entity->info.name, pad->index, stream);
> + 
> +-	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
> ++	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
> + 				     V4L2_SUBDEV_FORMAT_ACTIVE);
> + 	if (ret < 0) {
> + 		media_dbg(pad->entity->media,
> +@@ -705,8 +926,8 @@ static int set_format(struct media_pad *pad,
> + 	return 0;
> + }
> + 
> +-static int set_selection(struct media_pad *pad, unsigned int target,
> +-			 struct v4l2_rect *rect)
> ++static int set_selection(struct media_pad *pad, unsigned int stream,
> ++			 unsigned int target, struct v4l2_rect *rect)
> + {
> + 	int ret;
> + 
> +@@ -714,11 +935,11 @@ static int set_selection(struct media_pad *pad, unsigned int target,
> + 		return 0;
> + 
> + 	media_dbg(pad->entity->media,
> +-		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
> ++		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
> + 		  target, rect->left, rect->top, rect->width, rect->height,
> +-		  pad->entity->info.name, pad->index);
> ++		  pad->entity->info.name, pad->index, stream);
> + 
> +-	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
> ++	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
> + 					target, V4L2_SUBDEV_FORMAT_ACTIVE);
> + 	if (ret < 0) {
> + 		media_dbg(pad->entity->media,
> +@@ -734,7 +955,7 @@ static int set_selection(struct media_pad *pad, unsigned int target,
> + 	return 0;
> + }
> + 
> +-static int set_frame_interval(struct media_pad *pad,
> ++static int set_frame_interval(struct media_pad *pad, unsigned int stream,
> + 			      struct v4l2_fract *interval)
> + {
> + 	int ret;
> +@@ -743,11 +964,12 @@ static int set_frame_interval(struct media_pad *pad,
> + 		return 0;
> + 
> + 	media_dbg(pad->entity->media,
> +-		  "Setting up frame interval %u/%u on pad %s/%u\n",
> ++		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
> + 		  interval->numerator, interval->denominator,
> +-		  pad->entity->info.name, pad->index);
> ++		  pad->entity->info.name, pad->index, stream);
> + 
> +-	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
> ++	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
> ++					     stream);
> + 	if (ret < 0) {
> + 		media_dbg(pad->entity->media,
> + 			  "Unable to set frame interval: %s (%d)",
> +@@ -770,11 +992,13 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
> + 	struct v4l2_rect crop = { -1, -1, -1, -1 };
> + 	struct v4l2_rect compose = crop;
> + 	struct v4l2_fract interval = { 0, 0 };
> ++	unsigned int stream;
> + 	unsigned int i;
> + 	char *end;
> + 	int ret;
> + 
> +-	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
> ++	pad = v4l2_subdev_parse_pad_format(media, &stream,
> ++					   &format, &crop, &compose,
> + 					   &interval, p, &end);
> + 	if (pad == NULL) {
> + 		media_print_streampos(media, p, end);
> +@@ -783,30 +1007,29 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
> + 	}
> + 
> + 	if (pad->flags & MEDIA_PAD_FL_SINK) {
> +-		ret = set_format(pad, &format);
> ++		ret = set_format(pad, stream, &format);
> + 		if (ret < 0)
> + 			return ret;
> + 	}
> + 
> +-	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
> ++	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
> + 	if (ret < 0)
> + 		return ret;
> + 
> +-	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
> ++	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
> + 	if (ret < 0)
> + 		return ret;
> + 
> + 	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
> +-		ret = set_format(pad, &format);
> ++		ret = set_format(pad, stream, &format);
> + 		if (ret < 0)
> + 			return ret;
> + 	}
> + 
> +-	ret = set_frame_interval(pad, &interval);
> ++	ret = set_frame_interval(pad, stream, &interval);
> + 	if (ret < 0)
> + 		return ret;
> + 
> +-
> + 	/* If the pad is an output pad, automatically set the same format and
> + 	 * frame interval on the remote subdev input pads, if any.
> + 	 */
> +@@ -821,9 +1044,9 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
> + 			if (link->source == pad &&
> + 			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
> + 				remote_format = format;
> +-				set_format(link->sink, &remote_format);
> ++				set_format(link->sink, stream, &remote_format);
> + 
> +-				ret = set_frame_interval(link->sink, &interval);
> ++				ret = set_frame_interval(link->sink, stream, &interval);
> + 				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
> + 					return ret;
> + 			}
> +diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
> +index 84ee7a83..831136a0 100644
> +--- a/utils/media-ctl/media-ctl.c
> ++++ b/utils/media-ctl/media-ctl.c
> +@@ -28,6 +28,7 @@
> + #include <errno.h>
> + #include <fcntl.h>
> + #include <stdbool.h>
> ++#include <stdint.h>
> + #include <stdio.h>
> + #include <stdlib.h>
> + #include <string.h>
> +@@ -75,23 +76,43 @@ static void print_flags(const struct flag_name *flag_names, unsigned int num_ent
> + 	}
> + }
> + 
> ++static void v4l2_subdev_print_routes(struct media_entity *entity,
> ++				     struct v4l2_subdev_route *routes,
> ++				     unsigned int num_routes)
> ++{
> ++	unsigned int i;
> ++
> ++	for (i = 0; i < num_routes; i++) {
> ++		const struct v4l2_subdev_route *r = &routes[i];
> ++
> ++		if (i == 0)
> ++			printf("\troutes:\n");
> ++
> ++		printf("\t\t%u/%u -> %u/%u [%s]\n",
> ++		       r->sink_pad, r->sink_stream,
> ++		       r->source_pad, r->source_stream,
> ++		       r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE ? "ACTIVE" : "INACTIVE");
> ++	}
> ++}
> ++
> + static void v4l2_subdev_print_format(struct media_entity *entity,
> +-	unsigned int pad, enum v4l2_subdev_format_whence which)
> ++	unsigned int pad, unsigned int stream,
> ++	enum v4l2_subdev_format_whence which)
> + {
> + 	struct v4l2_mbus_framefmt format;
> + 	struct v4l2_fract interval = { 0, 0 };
> + 	struct v4l2_rect rect;
> + 	int ret;
> + 
> +-	ret = v4l2_subdev_get_format(entity, &format, pad, which);
> ++	ret = v4l2_subdev_get_format(entity, &format, pad, stream, which);
> + 	if (ret != 0)
> + 		return;
> + 
> +-	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad);
> ++	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad, stream);
> + 	if (ret != 0 && ret != -ENOTTY && ret != -EINVAL)
> + 		return;
> + 
> +-	printf("\t\t[fmt:%s/%ux%u",
> ++	printf("\t\t[stream:%u fmt:%s/%ux%u", stream,
> + 	       v4l2_subdev_pixelcode_to_string(format.code),
> + 	       format.width, format.height);
> + 
> +@@ -118,28 +139,28 @@ static void v4l2_subdev_print_format(struct media_entity *entity,
> + 			       v4l2_subdev_quantization_to_string(format.quantization));
> + 	}
> + 
> +-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
> ++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
> + 					V4L2_SEL_TGT_CROP_BOUNDS,
> + 					which);
> + 	if (ret == 0)
> + 		printf("\n\t\t crop.bounds:(%u,%u)/%ux%u", rect.left, rect.top,
> + 		       rect.width, rect.height);
> + 
> +-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
> ++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
> + 					V4L2_SEL_TGT_CROP,
> + 					which);
> + 	if (ret == 0)
> + 		printf("\n\t\t crop:(%u,%u)/%ux%u", rect.left, rect.top,
> + 		       rect.width, rect.height);
> + 
> +-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
> ++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
> + 					V4L2_SEL_TGT_COMPOSE_BOUNDS,
> + 					which);
> + 	if (ret == 0)
> + 		printf("\n\t\t compose.bounds:(%u,%u)/%ux%u",
> + 		       rect.left, rect.top, rect.width, rect.height);
> + 
> +-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
> ++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
> + 					V4L2_SEL_TGT_COMPOSE,
> + 					which);
> + 	if (ret == 0)
> +@@ -455,16 +476,58 @@ static void media_print_topology_dot(struct media_device *media)
> + }
> + 
> + static void media_print_pad_text(struct media_entity *entity,
> +-				 const struct media_pad *pad)
> ++				 const struct media_pad *pad,
> ++				 struct v4l2_subdev_route *routes,
> ++				 unsigned int num_routes)
> + {
> ++	unsigned int i;
> ++	uint64_t printed_streams_mask;
> ++
> + 	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
> + 		return;
> + 
> +-	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> +-	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> ++	if (!routes) {
> ++		v4l2_subdev_print_format(entity, pad->index, 0, V4L2_SUBDEV_FORMAT_ACTIVE);
> ++		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> ++
> ++		if (pad->flags & MEDIA_PAD_FL_SOURCE)
> ++			v4l2_subdev_print_subdev_dv(entity);
> ++
> ++		return;
> ++	}
> ++
> ++	printed_streams_mask = 0;
> ++
> ++	for (i = 0; i < num_routes; ++i) {
> ++		const struct v4l2_subdev_route *r = &routes[i];
> ++		unsigned int stream;
> + 
> +-	if (pad->flags & MEDIA_PAD_FL_SOURCE)
> +-		v4l2_subdev_print_subdev_dv(entity);
> ++		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
> ++			continue;
> ++
> ++		if (pad->flags & MEDIA_PAD_FL_SINK) {
> ++			if (r->sink_pad != pad->index)
> ++				continue;
> ++
> ++			stream = r->sink_stream;
> ++		} else {
> ++			if (r->source_pad != pad->index)
> ++				continue;
> ++
> ++			stream = r->source_stream;
> ++		}
> ++
> ++		if (printed_streams_mask & (1 << stream))
> ++			continue;
> ++
> ++		v4l2_subdev_print_format(entity, pad->index, stream, V4L2_SUBDEV_FORMAT_ACTIVE);
> ++		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> ++
> ++		if (pad->flags & MEDIA_PAD_FL_SOURCE)
> ++			v4l2_subdev_print_subdev_dv(entity);
> ++
> ++		printed_streams_mask |= (1 << stream);
> ++	}
> + }
> + 
> + static void media_print_topology_text_entity(struct media_device *media,
> +@@ -480,11 +543,17 @@ static void media_print_topology_text_entity(struct media_device *media,
> + 	unsigned int num_links = media_entity_get_links_count(entity);
> + 	unsigned int j, k;
> + 	unsigned int padding;
> ++	struct v4l2_subdev_route *routes = NULL;
> ++	unsigned int num_routes = 0;
> ++
> ++	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
> ++		v4l2_subdev_get_routing(entity, &routes, &num_routes);
> + 
> + 	padding = printf("- entity %u: ", info->id);
> +-	printf("%s (%u pad%s, %u link%s)\n", info->name,
> ++	printf("%s (%u pad%s, %u link%s, %u route%s)\n", info->name,
> + 	       info->pads, info->pads > 1 ? "s" : "",
> +-	       num_links, num_links > 1 ? "s" : "");
> ++	       num_links, num_links > 1 ? "s" : "",
> ++	       num_routes, num_routes > 1 ? "s" : "");
> + 	printf("%*ctype %s subtype %s flags %x\n", padding, ' ',
> + 	       media_entity_type_to_string(info->type),
> + 	       media_entity_subtype_to_string(info->type),
> +@@ -492,12 +561,15 @@ static void media_print_topology_text_entity(struct media_device *media,
> + 	if (devname)
> + 		printf("%*cdevice node name %s\n", padding, ' ', devname);
> + 
> ++	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
> ++		v4l2_subdev_print_routes(entity, routes, num_routes);
> ++
> + 	for (j = 0; j < info->pads; j++) {
> + 		const struct media_pad *pad = media_entity_get_pad(entity, j);
> + 
> + 		printf("\tpad%u: %s\n", j, media_pad_type_to_string(pad->flags));
> + 
> +-		media_print_pad_text(entity, pad);
> ++		media_print_pad_text(entity, pad, routes, num_routes);
> + 
> + 		for (k = 0; k < num_links; k++) {
> + 			const struct media_link *link = media_entity_get_link(entity, k);
> +@@ -521,6 +593,8 @@ static void media_print_topology_text_entity(struct media_device *media,
> + 		}
> + 	}
> + 	printf("\n");
> ++
> ++	free(routes);
> + }
> + 
> + static void media_print_topology_text(struct media_device *media)
> +@@ -594,14 +668,16 @@ int main(int argc, char **argv)
> + 
> + 	if (media_opts.fmt_pad) {
> + 		struct media_pad *pad;
> ++		unsigned int stream;
> ++		char *p;
> + 
> +-		pad = media_parse_pad(media, media_opts.fmt_pad, NULL);
> ++		pad = media_parse_pad_stream(media, media_opts.fmt_pad, &stream, &p);
> + 		if (pad == NULL) {
> + 			printf("Pad '%s' not found\n", media_opts.fmt_pad);
> + 			goto out;
> + 		}
> + 
> +-		v4l2_subdev_print_format(pad->entity, pad->index,
> ++		v4l2_subdev_print_format(pad->entity, pad->index, stream,
> + 					 V4L2_SUBDEV_FORMAT_ACTIVE);
> + 	}
> + 
> +@@ -685,6 +761,15 @@ int main(int argc, char **argv)
> + 		}
> + 	}
> + 
> ++	if (media_opts.routes) {
> ++		ret = v4l2_subdev_parse_setup_routes(media, media_opts.routes);
> ++		if (ret) {
> ++			printf("Unable to setup routes: %s (%d)\n",
> ++			       strerror(-ret), -ret);
> ++			goto out;
> ++		}
> ++	}
> ++
> + 	if (media_opts.interactive) {
> + 		while (1) {
> + 			char buffer[32];
> +diff --git a/utils/media-ctl/mediactl.h b/utils/media-ctl/mediactl.h
> +index af360518..c0fc2962 100644
> +--- a/utils/media-ctl/mediactl.h
> ++++ b/utils/media-ctl/mediactl.h
> +@@ -394,6 +394,22 @@ struct media_entity *media_parse_entity(struct media_device *media,
> + struct media_pad *media_parse_pad(struct media_device *media,
> + 				  const char *p, char **endp);
> + 
> ++/**
> ++ * @brief Parse string to a pad and stream on the media device.
> ++ * @param media - media device.
> ++ * @param p - input string
> ++ * @param stream - pointer to uint where the stream number is stored
> ++ * @param endp - pointer to string where parsing ended
> ++ *
> ++ * Parse NULL terminated string describing a pad and stream and return its struct
> ++ * media_pad instance and the stream number.
> ++ *
> ++ * @return Pointer to struct media_pad on success, NULL on failure.
> ++ */
> ++struct media_pad *media_parse_pad_stream(struct media_device *media,
> ++					 const char *p, unsigned int *stream,
> ++					 char **endp);
> ++
> + /**
> +  * @brief Parse string to a link on the media device.
> +  * @param media - media device.
> +diff --git a/utils/media-ctl/options.c b/utils/media-ctl/options.c
> +index 6d30d3dc..58ddec3c 100644
> +--- a/utils/media-ctl/options.c
> ++++ b/utils/media-ctl/options.c
> +@@ -63,6 +63,7 @@ static void usage(const char *argv0)
> + 	printf("    --get-v4l2 pad	Print the active format on a given pad\n");
> + 	printf("    --get-dv pad        Print detected and current DV timings on a given pad\n");
> + 	printf("    --set-dv pad	Configure DV timings on a given pad\n");
> ++	printf("-R, --set-routes routes Configure routes on a given subdev entity\n");
> + 	printf("-h, --help		Show verbose help and exit\n");
> + 	printf("-i, --interactive	Modify links interactively\n");
> + 	printf("-l, --links links	Comma-separated list of link descriptors to setup\n");
> +@@ -78,7 +79,7 @@ static void usage(const char *argv0)
> + 	printf("Links and formats are defined as\n");
> + 	printf("\tlinks           = link { ',' link } ;\n");
> + 	printf("\tlink            = pad '->' pad '[' flags ']' ;\n");
> +-	printf("\tpad             = entity ':' pad-number ;\n");
> ++	printf("\tpad             = entity ':' pad-number { '/' stream-number } ;\n");
> + 	printf("\tentity          = entity-number | ( '\"' entity-name '\"' ) ;\n");
> + 	printf("\n");
> + 	printf("\tv4l2            = pad '[' v4l2-properties ']' ;\n");
> +@@ -95,11 +96,16 @@ static void usage(const char *argv0)
> + 	printf("\trectangle       = '(' left ',' top, ')' '/' size ;\n");
> + 	printf("\tsize            = width 'x' height ;\n");
> + 	printf("\n");
> ++	printf("\troutes          = entity '[' route { ',' route } ']' ;\n");
> ++	printf("\troute           = pad-number '/' stream-number '->' pad-number '/' stream-number '[' route-flags ']' ;\n");
> ++	printf("\n");
> + 	printf("where the fields are\n");
> + 	printf("\tentity-number   Entity numeric identifier\n");
> + 	printf("\tentity-name     Entity name (string) \n");
> + 	printf("\tpad-number      Pad numeric identifier\n");
> ++	printf("\tstream-number   Stream numeric identifier\n");
> + 	printf("\tflags           Link flags (0: inactive, 1: active)\n");
> ++	printf("\troute-flags     Route flags (bitmask of route flags: active - 0x1, immutable - 0x2, source - 0x4)\n");
> + 	printf("\tfcc             Format FourCC\n");
> + 	printf("\twidth           Image width in pixels\n");
> + 	printf("\theight          Image height in pixels\n");
> +@@ -152,6 +158,7 @@ static struct option opts[] = {
> + 	{"get-v4l2", 1, 0, OPT_GET_FORMAT},
> + 	{"get-dv", 1, 0, OPT_GET_DV},
> + 	{"set-dv", 1, 0, OPT_SET_DV},
> ++	{"set-routes", 1, 0, 'R'},
> + 	{"help", 0, 0, 'h'},
> + 	{"interactive", 0, 0, 'i'},
> + 	{"links", 1, 0, 'l'},
> +@@ -237,7 +244,7 @@ int parse_cmdline(int argc, char **argv)
> + 	}
> + 
> + 	/* parse options */
> +-	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:",
> ++	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:R:",
> + 				  opts, NULL)) != -1) {
> + 		switch (opt) {
> + 		case 'd':
> +@@ -283,6 +290,10 @@ int parse_cmdline(int argc, char **argv)
> + 			media_opts.verbose = 1;
> + 			break;
> + 
> ++		case 'R':
> ++			media_opts.routes = optarg;
> ++			break;
> ++
> + 		case OPT_PRINT_DOT:
> + 			media_opts.print_dot = 1;
> + 			break;
> +diff --git a/utils/media-ctl/options.h b/utils/media-ctl/options.h
> +index b1751f56..8796f1b6 100644
> +--- a/utils/media-ctl/options.h
> ++++ b/utils/media-ctl/options.h
> +@@ -38,6 +38,7 @@ struct media_options
> + 	const char *fmt_pad;
> + 	const char *get_dv_pad;
> + 	const char *dv_pad;
> ++	const char *routes;
> + };
> + 
> + extern struct media_options media_opts;
> +diff --git a/utils/media-ctl/v4l2subdev.h b/utils/media-ctl/v4l2subdev.h
> +index a1813911..a8a6e7ad 100644
> +--- a/utils/media-ctl/v4l2subdev.h
> ++++ b/utils/media-ctl/v4l2subdev.h
> +@@ -64,7 +64,7 @@ void v4l2_subdev_close(struct media_entity *entity);
> +  * @return 0 on success, or a negative error code on failure.
> +  */
> + int v4l2_subdev_get_format(struct media_entity *entity,
> +-	struct v4l2_mbus_framefmt *format, unsigned int pad,
> ++	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
> + 	enum v4l2_subdev_format_whence which);
> + 
> + /**
> +@@ -86,6 +86,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
> +  */
> + int v4l2_subdev_set_format(struct media_entity *entity,
> + 	struct v4l2_mbus_framefmt *format, unsigned int pad,
> ++	unsigned int stream,
> + 	enum v4l2_subdev_format_whence which);
> + 
> + /**
> +@@ -107,8 +108,8 @@ int v4l2_subdev_set_format(struct media_entity *entity,
> +  * @return 0 on success, or a negative error code on failure.
> +  */
> + int v4l2_subdev_get_selection(struct media_entity *entity,
> +-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
> +-	enum v4l2_subdev_format_whence which);
> ++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
> ++	unsigned int target, enum v4l2_subdev_format_whence which);
> + 
> + /**
> +  * @brief Set a selection rectangle on a pad.
> +@@ -129,8 +130,40 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
> +  * @return 0 on success, or a negative error code on failure.
> +  */
> + int v4l2_subdev_set_selection(struct media_entity *entity,
> +-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
> +-	enum v4l2_subdev_format_whence which);
> ++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
> ++	unsigned int target, enum v4l2_subdev_format_whence which);
> ++
> ++/**
> ++ * @brief Get the routing table of a subdev media entity.
> ++ * @param entity - subdev-device media entity.
> ++ * @param routes - routes of the subdev.
> ++ * @param num_routes - number of routes.
> ++ *
> ++ * Get the routes of @a entity and return them in an allocated array in @a routes
> ++ * and the number of routes in @a num_routes.
> ++ *
> ++ * The caller is responsible for freeing the routes array after use.
> ++ *
> ++ * @return 0 on success, or a negative error code on failure.
> ++ */
> ++int v4l2_subdev_get_routing(struct media_entity *entity,
> ++			    struct v4l2_subdev_route **routes,
> ++			    unsigned int *num_routes);
> ++
> ++/**
> ++ * @brief Set the routing table of a subdev media entity.
> ++ * @param entity - subdev-device media entity.
> ++ * @param routes - routes of the subdev.
> ++ * @param num_routes - number of routes.
> ++ *
> ++ * Set the routes of @a entity. The routes are given in @a routes with the
> ++ * length of @a num_routes.
> ++ *
> ++ * @return 0 on success, or a negative error code on failure.
> ++ */
> ++int v4l2_subdev_set_routing(struct media_entity *entity,
> ++			    struct v4l2_subdev_route *route,
> ++			    unsigned int num_routes);
> + 
> + /**
> +  * @brief Query the digital video capabilities of a pad.
> +@@ -200,7 +233,7 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
> +  */
> + 
> + int v4l2_subdev_get_frame_interval(struct media_entity *entity,
> +-	struct v4l2_fract *interval, unsigned int pad);
> ++	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
> + 
> + /**
> +  * @brief Set the frame interval on a sub-device.
> +@@ -217,7 +250,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
> +  * @return 0 on success, or a negative error code on failure.
> +  */
> + int v4l2_subdev_set_frame_interval(struct media_entity *entity,
> +-	struct v4l2_fract *interval, unsigned int pad);
> ++	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
> + 
> + /**
> +  * @brief Parse a string and apply format, crop and frame interval settings.
> +@@ -235,6 +268,17 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
> +  */
> + int v4l2_subdev_parse_setup_formats(struct media_device *media, const char *p);
> + 
> ++/**
> ++ * @brief Parse a string and apply route settings.
> ++ * @param media - media device.
> ++ * @param p - input string
> ++ *
> ++ * Parse string @a p and apply route settings to a subdev.
> ++ *
> ++ * @return 0 on success, or a negative error code on failure.
> ++ */
> ++int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p);
> ++
> + /**
> +  * @brief Convert media bus pixel code to string.
> +  * @param code - input string
> +-- 
> +2.40.0
> +
> diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch
> new file mode 100644
> index 00000000..32eeb02a
> --- /dev/null
> +++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch
> @@ -0,0 +1,460 @@
> +From 2866c81d2597f47ed976928bc9c27942bbf095f0 Mon Sep 17 00:00:00 2001
> +From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +Date: Fri, 10 Feb 2023 13:55:46 +0200
> +Subject: [PATCH 3/3] v4l2-ctl/compliance: add routing and streams multiplexed
> + streams
> +
> +Add basic support for routing and streams.
> +
> +Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +---
> + utils/v4l2-compliance/v4l2-compliance.cpp   | 120 ++++++++++++++++----
> + utils/v4l2-compliance/v4l2-compliance.h     |   8 +-
> + utils/v4l2-compliance/v4l2-test-subdevs.cpp |  43 ++++++-
> + 3 files changed, 137 insertions(+), 34 deletions(-)
> +
> +diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
> +index 8aebae2e..63b5fbbb 100644
> +--- a/utils/v4l2-compliance/v4l2-compliance.cpp
> ++++ b/utils/v4l2-compliance/v4l2-compliance.cpp
> +@@ -1224,6 +1224,10 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
> + 	if (node.is_subdev()) {
> + 		bool has_source = false;
> + 		bool has_sink = false;
> ++		struct v4l2_subdev_routing sd_routing[2] = {};
> ++		struct v4l2_subdev_route sd_routes[2][256] = {};
> ++		bool has_routes = !!(subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
> ++		int ret;
> + 
> + 		node.frame_interval_pad = -1;
> + 		node.enum_frame_interval_pad = -1;
> +@@ -1235,6 +1239,22 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
> + 		}
> + 		node.is_passthrough_subdev = has_source && has_sink;
> + 
> ++		if (has_routes) {
> ++			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
> ++				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
> ++
> ++				sd_routing[which].which = which;
> ++				sd_routing[which].routes = (__u64)sd_routes[which];
> ++				sd_routing[which].num_routes = 256;
> ++
> ++				ret = doioctl(&node, VIDIOC_SUBDEV_G_ROUTING, &sd_routing[which]);
> ++				if (ret) {
> ++					fail("VIDIOC_SUBDEV_G_ROUTING: failed to get routing\n");
> ++					sd_routing[which].num_routes = 0;
> ++				}
> ++			}
> ++		}
> ++
> + 		for (unsigned pad = 0; pad < node.entity.pads; pad++) {
> + 			printf("Sub-Device ioctls (%s Pad %u):\n",
> + 			       (node.pads[pad].flags & MEDIA_PAD_FL_SINK) ?
> +@@ -1244,32 +1264,82 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
> + 			node.has_subdev_enum_fival = 0;
> + 			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
> + 			     which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
> +-				printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
> +-				       which ? "Active" : "Try",
> +-				       ok(testSubDevEnum(&node, which, pad)));
> +-				printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
> +-				       which ? "Active" : "Try",
> +-				       ok(testSubDevFormat(&node, which, pad)));
> +-				printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
> +-				       which ? "Active" : "Try",
> +-				       ok(testSubDevSelection(&node, which, pad)));
> +-				if (which)
> +-					printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
> +-					       ok(testSubDevFrameInterval(&node, pad)));
> ++				struct v4l2_subdev_routing dummy_routing;
> ++				struct v4l2_subdev_route dummy_routes[1];
> ++
> ++				const struct v4l2_subdev_routing *routing;
> ++				const struct v4l2_subdev_route *routes;
> ++
> ++				if (has_routes) {
> ++					routing = &sd_routing[which];
> ++					routes = sd_routes[which];
> ++				} else {
> ++					dummy_routing.num_routes = 1;
> ++					dummy_routing.routes = (__u64)&dummy_routes;
> ++					dummy_routes[0].source_pad = pad;
> ++					dummy_routes[0].source_stream = 0;
> ++					dummy_routes[0].sink_pad = pad;
> ++					dummy_routes[0].sink_stream = 0;
> ++					dummy_routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> ++
> ++					routing = &dummy_routing;
> ++					routes = dummy_routes;
> ++				}
> ++
> ++				for (unsigned i = 0; i < routing->num_routes; ++i) {
> ++					const struct v4l2_subdev_route *r = &routes[i];
> ++					unsigned stream;
> ++
> ++					if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
> ++						continue;
> ++
> ++					if ((node.pads[pad].flags & MEDIA_PAD_FL_SINK) &&
> ++					    (r->sink_pad == pad))
> ++						stream = r->sink_stream;
> ++					else if ((node.pads[pad].flags & MEDIA_PAD_FL_SOURCE) &&
> ++					    (r->source_pad == pad))
> ++						stream = r->source_stream;
> ++					else
> ++						continue;
> ++
> ++					printf("\t%s Stream %u\n",which ? "Active" : "Try",
> ++					       stream);
> ++
> ++					printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
> ++					       which ? "Active" : "Try",
> ++					       ok(testSubDevEnum(&node, which, pad, stream)));
> ++					printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
> ++					       which ? "Active" : "Try",
> ++					       ok(testSubDevFormat(&node, which, pad, stream)));
> ++					printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
> ++					       which ? "Active" : "Try",
> ++					       ok(testSubDevSelection(&node, which, pad, stream)));
> ++					if (which)
> ++						printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
> ++						       ok(testSubDevFrameInterval(&node, pad, stream)));
> ++				}
> ++			}
> ++
> ++			/*
> ++			 * These tests do not make sense for subdevs with multiplexed streams,
> ++			 * as the try & active cases may have different routing and thus different
> ++			 * behavior.
> ++			 */
> ++			if (!has_routes) {
> ++				if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
> ++					fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
> ++				if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
> ++					fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
> ++				if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
> ++					fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
> ++				if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
> ++					fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
> ++				if (node.has_subdev_selection && node.has_subdev_selection < 3)
> ++					fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
> ++				if (node.has_subdev_selection &&
> ++				    node.has_subdev_selection != node.has_subdev_fmt)
> ++					fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
> + 			}
> +-			if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
> +-				fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
> +-			if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
> +-				fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
> +-			if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
> +-				fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
> +-			if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
> +-				fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
> +-			if (node.has_subdev_selection && node.has_subdev_selection < 3)
> +-				fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
> +-			if (node.has_subdev_selection &&
> +-			    node.has_subdev_selection != node.has_subdev_fmt)
> +-				fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
> + 			printf("\n");
> + 		}
> + 	}
> +diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
> +index e574c06c..67b3521e 100644
> +--- a/utils/v4l2-compliance/v4l2-compliance.h
> ++++ b/utils/v4l2-compliance/v4l2-compliance.h
> +@@ -373,10 +373,10 @@ int testDecoder(struct node *node);
> + 
> + // SubDev ioctl tests
> + int testSubDevCap(struct node *node);
> +-int testSubDevEnum(struct node *node, unsigned which, unsigned pad);
> +-int testSubDevFormat(struct node *node, unsigned which, unsigned pad);
> +-int testSubDevSelection(struct node *node, unsigned which, unsigned pad);
> +-int testSubDevFrameInterval(struct node *node, unsigned pad);
> ++int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream);
> ++int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
> ++int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
> ++int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
> + 
> + // Buffer ioctl tests
> + int testReqBufs(struct node *node);
> +diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> +index f3d85771..07192bda 100644
> +--- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> ++++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> +@@ -25,7 +25,7 @@
> + 
> + #include "v4l2-compliance.h"
> + 
> +-#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV)
> ++#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV | V4L2_SUBDEV_CAP_STREAMS)
> + 
> + int testSubDevCap(struct node *node)
> + {
> +@@ -54,6 +54,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
> + 	memset(&fie, 0, sizeof(fie));
> + 	fie.which = which;
> + 	fie.pad = pad;
> ++	fie.stream = 0;
> + 	fie.code = code;
> + 	fie.width = width;
> + 	fie.height = height;
> +@@ -83,6 +84,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
> + 	memset(&fie, 0xff, sizeof(fie));
> + 	fie.which = which;
> + 	fie.pad = pad;
> ++	fie.stream = 0;
> + 	fie.code = code;
> + 	fie.width = width;
> + 	fie.height = height;
> +@@ -128,6 +130,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
> + 	memset(&fse, 0, sizeof(fse));
> + 	fse.which = which;
> + 	fse.pad = pad;
> ++	fse.stream = 0;
> + 	fse.code = code;
> + 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse);
> + 	node->has_subdev_enum_fsize |= (ret != ENOTTY) << which;
> +@@ -137,6 +140,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
> + 		memset(&fie, 0, sizeof(fie));
> + 		fie.which = which;
> + 		fie.pad = pad;
> ++		fie.stream = 0;
> + 		fie.code = code;
> + 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
> + 		return ret;
> +@@ -152,6 +156,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
> + 	memset(&fse, 0xff, sizeof(fse));
> + 	fse.which = which;
> + 	fse.pad = pad;
> ++	fse.stream = 0;
> + 	fse.code = code;
> + 	fse.index = 0;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse));
> +@@ -195,7 +200,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
> + 	return 0;
> + }
> + 
> +-int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> ++int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream)
> + {
> + 	struct v4l2_subdev_mbus_code_enum mbus_core_enum;
> + 	unsigned num_codes;
> +@@ -204,6 +209,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> + 	memset(&mbus_core_enum, 0, sizeof(mbus_core_enum));
> + 	mbus_core_enum.which = which;
> + 	mbus_core_enum.pad = pad;
> ++	mbus_core_enum.stream = stream;
> + 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum);
> + 	node->has_subdev_enum_code |= (ret != ENOTTY) << which;
> + 	if (ret == ENOTTY) {
> +@@ -214,8 +220,10 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> + 		memset(&fie, 0, sizeof(fie));
> + 		fse.which = which;
> + 		fse.pad = pad;
> ++		fse.stream = stream;
> + 		fie.which = which;
> + 		fie.pad = pad;
> ++		fie.stream = stream;
> + 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse) != ENOTTY);
> + 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
> + 		return ret;
> +@@ -226,16 +234,19 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> + 	mbus_core_enum.index = ~0;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
> + 	mbus_core_enum.pad = node->entity.pads;
> ++	mbus_core_enum.stream = stream;
> + 	mbus_core_enum.index = 0;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
> + 	memset(&mbus_core_enum, 0xff, sizeof(mbus_core_enum));
> + 	mbus_core_enum.which = which;
> + 	mbus_core_enum.pad = pad;
> ++	mbus_core_enum.stream = stream;
> + 	mbus_core_enum.index = 0;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum));
> + 	fail_on_test(check_0(mbus_core_enum.reserved, sizeof(mbus_core_enum.reserved)));
> + 	fail_on_test(mbus_core_enum.code == ~0U);
> + 	fail_on_test(mbus_core_enum.pad != pad);
> ++	fail_on_test(mbus_core_enum.stream != stream);
> + 	fail_on_test(mbus_core_enum.index);
> + 	fail_on_test(mbus_core_enum.which != which);
> + 	do {
> +@@ -252,6 +263,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> + 		fail_on_test(!mbus_core_enum.code);
> + 		fail_on_test(mbus_core_enum.which != which);
> + 		fail_on_test(mbus_core_enum.pad != pad);
> ++		fail_on_test(mbus_core_enum.stream != stream);
> + 		fail_on_test(mbus_core_enum.index != i);
> + 
> + 		ret = testSubDevEnumFrameSize(node, which, pad, mbus_core_enum.code);
> +@@ -260,7 +272,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
> + 	return 0;
> + }
> + 
> +-int testSubDevFrameInterval(struct node *node, unsigned pad)
> ++int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream)
> + {
> + 	struct v4l2_subdev_frame_interval fival;
> + 	struct v4l2_fract ival;
> +@@ -268,6 +280,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
> + 
> + 	memset(&fival, 0xff, sizeof(fival));
> + 	fival.pad = pad;
> ++	fival.stream = stream;
> + 	ret = doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival);
> + 	if (ret == ENOTTY) {
> + 		fail_on_test(node->enum_frame_interval_pad >= 0);
> +@@ -279,6 +292,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
> + 	node->frame_interval_pad = pad;
> + 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
> + 	fail_on_test(fival.pad != pad);
> ++	fail_on_test(fival.stream != stream);
> + 	fail_on_test(!fival.interval.numerator);
> + 	fail_on_test(!fival.interval.denominator);
> + 	fail_on_test(fival.interval.numerator == ~0U || fival.interval.denominator == ~0U);
> +@@ -290,20 +304,25 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
> + 	}
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
> + 	fail_on_test(fival.pad != pad);
> ++	fail_on_test(fival.stream != stream);
> + 	fail_on_test(ival.numerator != fival.interval.numerator);
> + 	fail_on_test(ival.denominator != fival.interval.denominator);
> + 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
> + 	memset(&fival, 0, sizeof(fival));
> + 	fival.pad = pad;
> ++	fival.stream = stream;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival));
> + 	fail_on_test(fival.pad != pad);
> ++	fail_on_test(fival.stream != stream);
> + 	fail_on_test(ival.numerator != fival.interval.numerator);
> + 	fail_on_test(ival.denominator != fival.interval.denominator);
> + 
> + 	fival.pad = node->entity.pads;
> ++	fival.stream = stream;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) != EINVAL);
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) != EINVAL);
> + 	fival.pad = pad;
> ++	fival.stream = stream;
> + 	fival.interval = ival;
> + 	fival.interval.numerator = 0;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
> +@@ -340,7 +359,7 @@ static int checkMBusFrameFmt(struct node *node, struct v4l2_mbus_framefmt &fmt)
> + 	return 0;
> + }
> + 
> +-int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
> ++int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream)
> + {
> + 	struct v4l2_subdev_format fmt;
> + 	struct v4l2_subdev_format s_fmt;
> +@@ -349,6 +368,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
> + 	memset(&fmt, 0, sizeof(fmt));
> + 	fmt.which = which;
> + 	fmt.pad = pad;
> ++	fmt.stream = stream;
> + 	ret = doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt);
> + 	node->has_subdev_fmt |= (ret != ENOTTY) << which;
> + 	if (ret == ENOTTY) {
> +@@ -359,14 +379,17 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
> + 	fmt.which = 0;
> + 	fmt.pad = node->entity.pads;
> ++	fmt.stream = stream;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
> + 	memset(&fmt, 0xff, sizeof(fmt));
> + 	fmt.which = which;
> + 	fmt.pad = pad;
> ++	fmt.stream = stream;
> + 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt));
> + 	fail_on_test(check_0(fmt.reserved, sizeof(fmt.reserved)));
> + 	fail_on_test(fmt.which != which);
> + 	fail_on_test(fmt.pad != pad);
> ++	fail_on_test(fmt.stream != stream);
> + 	fail_on_test(checkMBusFrameFmt(node, fmt.format));
> + 	s_fmt = fmt;
> + 	memset(s_fmt.reserved, 0xff, sizeof(s_fmt.reserved));
> +@@ -379,6 +402,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
> + 	fail_on_test(ret && ret != ENOTTY);
> + 	fail_on_test(s_fmt.which != which);
> + 	fail_on_test(s_fmt.pad != pad);
> ++	fail_on_test(s_fmt.stream != stream);
> + 	if (ret) {
> + 		warn("VIDIOC_SUBDEV_G_FMT is supported but not VIDIOC_SUBDEV_S_FMT\n");
> + 		return 0;
> +@@ -423,7 +447,7 @@ static target_info targets[] = {
> + 	{ ~0U },
> + };
> + 
> +-int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> ++int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream)
> + {
> + 	struct v4l2_subdev_selection sel;
> + 	struct v4l2_subdev_selection s_sel;
> +@@ -435,10 +459,12 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 	targets[V4L2_SEL_TGT_NATIVE_SIZE].readonly = is_sink;
> + 	memset(&crop, 0, sizeof(crop));
> + 	crop.pad = pad;
> ++	crop.stream = stream;
> + 	crop.which = which;
> + 	memset(&sel, 0, sizeof(sel));
> + 	sel.which = which;
> + 	sel.pad = pad;
> ++	sel.stream = stream;
> + 	sel.target = V4L2_SEL_TGT_CROP;
> + 	ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
> + 	node->has_subdev_selection |= (ret != ENOTTY) << which;
> +@@ -451,6 +477,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 	fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
> + 	fail_on_test(crop.which != which);
> + 	fail_on_test(crop.pad != pad);
> ++	fail_on_test(crop.stream != stream);
> + 	fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
> + 
> + 	for (unsigned tgt = 0; targets[tgt].target != ~0U; tgt++) {
> +@@ -458,6 +485,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 		memset(&sel, 0xff, sizeof(sel));
> + 		sel.which = which;
> + 		sel.pad = pad;
> ++		sel.stream = stream;
> + 		sel.target = tgt;
> + 		ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
> + 		targets[tgt].found = !ret;
> +@@ -469,6 +497,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 		fail_on_test(check_0(sel.reserved, sizeof(sel.reserved)));
> + 		fail_on_test(sel.which != which);
> + 		fail_on_test(sel.pad != pad);
> ++		fail_on_test(sel.stream != stream);
> + 		fail_on_test(sel.target != tgt);
> + 		fail_on_test(!sel.r.width);
> + 		fail_on_test(sel.r.width == ~0U);
> +@@ -480,9 +509,11 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
> + 		sel.which = 0;
> + 		sel.pad = node->entity.pads;
> ++		sel.stream = stream;
> + 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
> + 		sel.which = which;
> + 		sel.pad = pad;
> ++		sel.stream = stream;
> + 		s_sel = sel;
> + 		memset(s_sel.reserved, 0xff, sizeof(s_sel.reserved));
> + 		ret = doioctl(node, VIDIOC_SUBDEV_S_SELECTION, &s_sel);
> +@@ -496,6 +527,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 				fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
> + 				fail_on_test(crop.which != which);
> + 				fail_on_test(crop.pad != pad);
> ++				fail_on_test(crop.stream != stream);
> + 				fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
> + 			}
> + 		}
> +@@ -504,6 +536,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
> + 		fail_on_test(!ret && targets[tgt].readonly);
> + 		fail_on_test(s_sel.which != which);
> + 		fail_on_test(s_sel.pad != pad);
> ++		fail_on_test(s_sel.stream != stream);
> + 		if (ret && !targets[tgt].readonly && tgt != V4L2_SEL_TGT_NATIVE_SIZE)
> + 			warn("VIDIOC_SUBDEV_G_SELECTION is supported for target %u but not VIDIOC_SUBDEV_S_SELECTION\n", tgt);
> + 		if (ret)
> +-- 
> +2.40.0
> +
> diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
> index c604ebbc..a2ebb0ea 100644
> --- a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
> +++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
> @@ -31,6 +31,10 @@ SRC_URI = "\
>      file://0002-original-patch-mediactl-pkgconfig.patch \
>      file://0003-original-patch-export-mediactl-headers.patch \
>      file://0004-Do-not-use-getsubopt.patch \
> +    file://0001-v4l2-ctl-Add-routing-and-streams-support.patch \
> +    file://0002-media-ctl-add-support-for-routes-and-streams.patch \
> +    file://0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch \
> +    file://0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch \
>  "
>  
>  SRC_URI[md5sum] = "8ba9c73c4319b6afab5fa4358edc43de"
diff mbox series

Patch

diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
new file mode 100644
index 00000000..00f54456
--- /dev/null
+++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch
@@ -0,0 +1,62 @@ 
+From 26e2a60d29456a9cc6acb16ea19039414808bc5e Mon Sep 17 00:00:00 2001
+From: Jai Luthra <j-luthra@ti.com>
+Date: Tue, 5 Jul 2022 16:23:39 +0530
+Subject: [PATCH] media-ctl: add support for RGBIr bayer formats
+
+Signed-off-by: Jai Luthra <j-luthra@ti.com>
+---
+ include/linux/media-bus-format.h | 10 +++++++++-
+ include/linux/videodev2.h        |  9 +++++++++
+ 2 files changed, 18 insertions(+), 1 deletion(-)
+
+diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h
+index ca9a24c8..cbdf3798 100644
+--- a/include/linux/media-bus-format.h
++++ b/include/linux/media-bus-format.h
+@@ -117,7 +117,7 @@
+ #define MEDIA_BUS_FMT_YUV16_1X48		0x202a
+ #define MEDIA_BUS_FMT_UYYVYY16_0_5X48		0x202b
+ 
+-/* Bayer - next is	0x3021 */
++/* Bayer - next is	0x3029 */
+ #define MEDIA_BUS_FMT_SBGGR8_1X8		0x3001
+ #define MEDIA_BUS_FMT_SGBRG8_1X8		0x3013
+ #define MEDIA_BUS_FMT_SGRBG8_1X8		0x3002
+@@ -150,6 +150,14 @@
+ #define MEDIA_BUS_FMT_SGBRG16_1X16		0x301e
+ #define MEDIA_BUS_FMT_SGRBG16_1X16		0x301f
+ #define MEDIA_BUS_FMT_SRGGB16_1X16		0x3020
++#define MEDIA_BUS_FMT_SRGGI10_1X10		0x3021
++#define MEDIA_BUS_FMT_SGRIG10_1X10		0x3022
++#define MEDIA_BUS_FMT_SBGGI10_1X10		0x3023
++#define MEDIA_BUS_FMT_SGBIG10_1X10		0x3024
++#define MEDIA_BUS_FMT_SGIRG10_1X10		0x3025
++#define MEDIA_BUS_FMT_SIGGR10_1X10		0x3026
++#define MEDIA_BUS_FMT_SGIBG10_1X10		0x3027
++#define MEDIA_BUS_FMT_SIGGB10_1X10		0x3028
+ 
+ /* JPEG compressed formats - next is	0x4002 */
+ #define MEDIA_BUS_FMT_JPEG_1X8			0x4001
+diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
+index 5eb96692..093104ab 100644
+--- a/include/linux/videodev2.h
++++ b/include/linux/videodev2.h
+@@ -682,6 +682,15 @@ struct v4l2_pix_format {
+ #define V4L2_PIX_FMT_SGBRG16 v4l2_fourcc('G', 'B', '1', '6') /* 16  GBGB.. RGRG.. */
+ #define V4L2_PIX_FMT_SGRBG16 v4l2_fourcc('G', 'R', '1', '6') /* 16  GRGR.. BGBG.. */
+ #define V4L2_PIX_FMT_SRGGB16 v4l2_fourcc('R', 'G', '1', '6') /* 16  RGRG.. GBGB.. */
++	/* 10bit raw bayer with IR (4x4) */
++#define V4L2_PIX_FMT_SRGGI10 v4l2_fourcc('R', 'G', 'I', '0') /* 10 RGBG.. GIrGIr.. */
++#define V4L2_PIX_FMT_SGRIG10 v4l2_fourcc('G', 'R', 'I', '0') /* 10 GRGB.. IrGIrG.. */
++#define V4L2_PIX_FMT_SBGGI10 v4l2_fourcc('B', 'G', 'I', '0') /* 10 BGRG.. GIrGIr.. */
++#define V4L2_PIX_FMT_SGBIG10 v4l2_fourcc('G', 'B', 'I', '0') /* 10 GBGR.. IrGIrG.. */
++#define V4L2_PIX_FMT_SGIRG10 v4l2_fourcc('G', 'I', 'R', '0') /* 10 GIrGIr.. RGBG.. */
++#define V4L2_PIX_FMT_SIGGR10 v4l2_fourcc('I', 'G', 'R', '0') /* 10 IrGIrG.. GRGB.. */
++#define V4L2_PIX_FMT_SGIBG10 v4l2_fourcc('G', 'I', 'B', '0') /* 10 GIrGIr.. BGRG.. */
++#define V4L2_PIX_FMT_SIGGB10 v4l2_fourcc('I', 'G', 'B', '0') /* 10 IrGIrG.. GBGR.. */
+ 
+ /* HSV formats */
+ #define V4L2_PIX_FMT_HSV24 v4l2_fourcc('H', 'S', 'V', '3')
+-- 
+2.40.0
+
diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
new file mode 100644
index 00000000..90f15485
--- /dev/null
+++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0001-v4l2-ctl-Add-routing-and-streams-support.patch
@@ -0,0 +1,619 @@ 
+From 3b57a10f899403acd877683ca0247f2a9eba8850 Mon Sep 17 00:00:00 2001
+From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+Date: Fri, 10 Feb 2023 13:55:44 +0200
+Subject: [PATCH 1/3] v4l2-ctl: Add routing and streams support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support to get and set subdev routes and to get and set
+configurations per stream.
+
+Based on work from Jacopo Mondi <jacopo@jmondi.org> and
+Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>.
+
+Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+---
+ utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 288 +++++++++++++++++++++++++----
+ utils/v4l2-ctl/v4l2-ctl.cpp        |   2 +
+ utils/v4l2-ctl/v4l2-ctl.h          |   2 +
+ 3 files changed, 259 insertions(+), 33 deletions(-)
+
+diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+index 33cc1342..81236451 100644
+--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
++++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+@@ -1,5 +1,13 @@
+ #include "v4l2-ctl.h"
+ 
++#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
++
++/*
++ * The max value comes from a check in the kernel source code
++ * drivers/media/v4l2-core/v4l2-ioctl.c check_array_args()
++ */
++#define NUM_ROUTES_MAX 256
++
+ struct mbus_name {
+ 	const char *name;
+ 	__u32 code;
+@@ -19,45 +27,57 @@ static const struct mbus_name mbus_names[] = {
+ #define SelectionFlags 		(1L<<4)
+ 
+ static __u32 list_mbus_codes_pad;
++static __u32 list_mbus_codes_stream = 0;
+ static __u32 get_fmt_pad;
++static __u32 get_fmt_stream = 0;
+ static __u32 get_sel_pad;
++static __u32 get_sel_stream = 0;
+ static __u32 get_fps_pad;
++static __u32 get_fps_stream = 0;
+ static int get_sel_target = -1;
+ static unsigned int set_selection;
+ static struct v4l2_subdev_selection vsel;
+ static unsigned int set_fmt;
+ static __u32 set_fmt_pad;
++static __u32 set_fmt_stream = 0;
+ static struct v4l2_mbus_framefmt ffmt;
+ static struct v4l2_subdev_frame_size_enum frmsize;
+ static struct v4l2_subdev_frame_interval_enum frmival;
+ static __u32 set_fps_pad;
++static __u32 set_fps_stream = 0;
+ static double set_fps;
++static struct v4l2_subdev_routing routing;
++static struct v4l2_subdev_route routes[NUM_ROUTES_MAX];
+ 
+ void subdev_usage()
+ {
+ 	printf("\nSub-Device options:\n"
+-	       "  --list-subdev-mbus-codes <pad>\n"
++	       "  --list-subdev-mbus-codes pad=<pad>,stream=<stream>\n"
+ 	       "                      display supported mediabus codes for this pad (0 is default)\n"
+ 	       "                      [VIDIOC_SUBDEV_ENUM_MBUS_CODE]\n"
+-	       "  --list-subdev-framesizes pad=<pad>,code=<code>\n"
++	       "  --list-subdev-framesizes pad=<pad>,stream=<stream>,code=<code>\n"
+ 	       "                     list supported framesizes for this pad and code\n"
+ 	       "                     [VIDIOC_SUBDEV_ENUM_FRAME_SIZE]\n"
+ 	       "                     <code> is the value of the mediabus code\n"
+-	       "  --list-subdev-frameintervals pad=<pad>,width=<w>,height=<h>,code=<code>\n"
++	       "  --list-subdev-frameintervals pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>\n"
+ 	       "                     list supported frame intervals for this pad and code and\n"
+ 	       "                     the given width and height [VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL]\n"
+ 	       "                     <code> is the value of the mediabus code\n"
+-	       "  --get-subdev-fmt [<pad>]\n"
+-	       "     		     query the frame format for the given pad [VIDIOC_SUBDEV_G_FMT]\n"
+-	       "  --get-subdev-selection pad=<pad>,target=<target>\n"
++	       "  --get-subdev-fmt pad=<pad>,stream=<stream>\n"
++	       "     		     query the frame format for the given pad and optional stream [VIDIOC_SUBDEV_G_FMT]\n"
++	       "		     <pad> the pad to get the format from\n"
++	       "		     <stream> the stream to get the format from (0 if not specified)\n"
++	       "  --get-subdev-selection pad=<pad>,stream=<stream>,target=<target>\n"
+ 	       "                     query the frame selection rectangle [VIDIOC_SUBDEV_G_SELECTION]\n"
+ 	       "                     See --set-subdev-selection command for the valid <target> values.\n"
+-	       "  --get-subdev-fps [<pad>]\n"
++	       "  --get-subdev-fps pad=<pad>,stream=<stream>\n"
+ 	       "                     query the frame rate [VIDIOC_SUBDEV_G_FRAME_INTERVAL]\n"
+ 	       "  --set-subdev-fmt   (for testing only, otherwise use media-ctl)\n"
+-	       "  --try-subdev-fmt pad=<pad>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
++	       "  --try-subdev-fmt pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
+ 	       "                   xfer=<xf>,ycbcr=<y>,hsv=<hsv>,quantization=<q>\n"
+-	       "                     set the frame format [VIDIOC_SUBDEV_S_FMT]\n"
++	       "                     set the frame format for the given pad and optional stream [VIDIOC_SUBDEV_S_FMT]\n"
++	       "                     <pad> the pad to get the format from\n"
++	       "                     <stream> the stream to get the format from (0 if not specified)\n"
+ 	       "                     <code> is the value of the mediabus code\n"
+ 	       "                     <f> can be one of the following field layouts:\n"
+ 	       "                       any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
+@@ -74,14 +94,30 @@ void subdev_usage()
+ 	       "                     <q> can be one of the following quantization methods:\n"
+ 	       "                       default, full-range, lim-range\n"
+ 	       "  --set-subdev-selection (for testing only, otherwise use media-ctl)\n"
+-	       "  --try-subdev-selection pad=<pad>,target=<target>,flags=<flags>,\n"
++	       "  --try-subdev-selection pad=<pad>,stream=<stream>,target=<target>,flags=<flags>,\n"
+ 	       "                         top=<x>,left=<y>,width=<w>,height=<h>\n"
+ 	       "                     set the video capture selection rectangle [VIDIOC_SUBDEV_S_SELECTION]\n"
+ 	       "                     target=crop|crop_bounds|crop_default|compose|compose_bounds|\n"
+ 	       "                            compose_default|compose_padded|native_size\n"
+ 	       "                     flags=le|ge|keep-config\n"
+-	       "  --set-subdev-fps pad=<pad>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
++	       "  --set-subdev-fps pad=<pad>,stream=<stream>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
+ 	       "                     set the frame rate [VIDIOC_SUBDEV_S_FRAME_INTERVAL]\n"
++	       "  --get-routing      Print the route topology\n"
++	       "  --set-routing <routes>\n"
++	       "                     Comma-separated list of route descriptors to setup\n"
++	       "\n"
++	       "Routes are defined as\n"
++	       "	routes		= route { ',' route } ;\n"
++	       "	route		= sink '->' source '[' flags ']' ;\n"
++	       "	sink		= sink-pad '/' sink-stream ;\n"
++	       "	source		= source-pad '/' source-stream ;\n"
++	       "\n"
++	       "where\n"
++	       "	sink-pad	= Pad numeric identifier for sink\n"
++	       "	sink-stream	= Stream numeric identifier for sink\n"
++	       "	source-pad	= Pad numeric identifier for source\n"
++	       "	source-stream	= Stream numeric identifier for source\n"
++	       "	flags		= Route flags (0: inactive, 1: active)\n"
+ 	       );
+ }
+ 
+@@ -91,14 +127,33 @@ void subdev_cmd(int ch, char *optarg)
+ 
+ 	switch (ch) {
+ 	case OptListSubDevMBusCodes:
+-		if (optarg)
+-			list_mbus_codes_pad = strtoul(optarg, nullptr, 0);
++		subs = optarg;
++		while (subs && *subs != '\0') {
++			static constexpr const char *subopts[] = {
++				"pad",
++				"stream",
++				nullptr
++			};
++
++			switch (parse_subopt(&subs, subopts, &value)) {
++			case 0:
++				list_mbus_codes_pad = strtoul(value, nullptr, 0);
++				break;
++			case 1:
++				list_mbus_codes_stream = strtoul(value, nullptr, 0);
++				break;
++			default:
++				subdev_usage();
++				std::exit(EXIT_FAILURE);
++			}
++		}
+ 		break;
+ 	case OptListSubDevFrameSizes:
+ 		subs = optarg;
+ 		while (*subs != '\0') {
+ 			static constexpr const char *subopts[] = {
+ 				"pad",
++				"stream",
+ 				"code",
+ 				nullptr
+ 			};
+@@ -108,6 +163,9 @@ void subdev_cmd(int ch, char *optarg)
+ 				frmsize.pad = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 1:
++				frmsize.stream = strtoul(value, nullptr, 0);
++				break;
++			case 2:
+ 				frmsize.code = strtoul(value, nullptr, 0);
+ 				break;
+ 			default:
+@@ -121,6 +179,7 @@ void subdev_cmd(int ch, char *optarg)
+ 		while (*subs != '\0') {
+ 			static constexpr const char *subopts[] = {
+ 				"pad",
++				"stream",
+ 				"code",
+ 				"width",
+ 				"height",
+@@ -132,12 +191,15 @@ void subdev_cmd(int ch, char *optarg)
+ 				frmival.pad = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 1:
+-				frmival.code = strtoul(value, nullptr, 0);
++				frmival.stream = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 2:
+-				frmival.width = strtoul(value, nullptr, 0);
++				frmival.code = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 3:
++				frmival.width = strtoul(value, nullptr, 0);
++				break;
++			case 4:
+ 				frmival.height = strtoul(value, nullptr, 0);
+ 				break;
+ 			default:
+@@ -147,14 +209,33 @@ void subdev_cmd(int ch, char *optarg)
+ 		}
+ 		break;
+ 	case OptGetSubDevFormat:
+-		if (optarg)
+-			get_fmt_pad = strtoul(optarg, nullptr, 0);
++		subs = optarg;
++		while (subs && *subs != '\0') {
++			static constexpr const char *subopts[] = {
++				"pad",
++				"stream",
++				nullptr
++			};
++
++			switch (parse_subopt(&subs, subopts, &value)) {
++			case 0:
++				get_fmt_pad = strtoul(value, nullptr, 0);
++				break;
++			case 1:
++				get_fmt_stream = strtoul(value, nullptr, 0);
++				break;
++			default:
++				subdev_usage();
++				std::exit(EXIT_FAILURE);
++			}
++		}
+ 		break;
+ 	case OptGetSubDevSelection:
+ 		subs = optarg;
+ 		while (*subs != '\0') {
+ 			static constexpr const char *subopts[] = {
+ 				"pad",
++				"stream",
+ 				"target",
+ 				nullptr
+ 			};
+@@ -165,6 +246,9 @@ void subdev_cmd(int ch, char *optarg)
+ 				get_sel_pad = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 1:
++				get_sel_stream = strtoul(value, nullptr, 0);
++				break;
++			case 2:
+ 				if (parse_selection_target(value, target)) {
+ 					fprintf(stderr, "Unknown selection target\n");
+ 					subdev_usage();
+@@ -179,8 +263,26 @@ void subdev_cmd(int ch, char *optarg)
+ 		}
+ 		break;
+ 	case OptGetSubDevFPS:
+-		if (optarg)
+-			get_fps_pad = strtoul(optarg, nullptr, 0);
++		subs = optarg;
++		while (subs && *subs != '\0') {
++			static constexpr const char *subopts[] = {
++				"pad",
++				"stream",
++				nullptr
++			};
++
++			switch (parse_subopt(&subs, subopts, &value)) {
++			case 0:
++				get_fps_pad = strtoul(value, nullptr, 0);
++				break;
++			case 1:
++				get_fps_stream = strtoul(value, nullptr, 0);
++				break;
++			default:
++				subdev_usage();
++				std::exit(EXIT_FAILURE);
++			}
++		}
+ 		break;
+ 	case OptSetSubDevFormat:
+ 	case OptTrySubDevFormat:
+@@ -198,6 +300,7 @@ void subdev_cmd(int ch, char *optarg)
+ 				"quantization",
+ 				"xfer",
+ 				"pad",
++				"stream",
+ 				nullptr
+ 			};
+ 
+@@ -244,6 +347,9 @@ void subdev_cmd(int ch, char *optarg)
+ 			case 9:
+ 				set_fmt_pad = strtoul(value, nullptr, 0);
+ 				break;
++			case 10:
++				set_fmt_stream = strtoul(value, nullptr, 0);
++				break;
+ 			default:
+ 				fprintf(stderr, "Unknown option\n");
+ 				subdev_usage();
+@@ -264,6 +370,7 @@ void subdev_cmd(int ch, char *optarg)
+ 				"width",
+ 				"height",
+ 				"pad",
++				"stream",
+ 				nullptr
+ 			};
+ 
+@@ -298,6 +405,9 @@ void subdev_cmd(int ch, char *optarg)
+ 			case 6:
+ 				vsel.pad = strtoul(value, nullptr, 0);
+ 				break;
++			case 7:
++				vsel.stream = strtoul(value, nullptr, 0);
++				break;
+ 			default:
+ 				fprintf(stderr, "Unknown option\n");
+ 				subdev_usage();
+@@ -311,6 +421,7 @@ void subdev_cmd(int ch, char *optarg)
+ 		while (*subs != '\0') {
+ 			static constexpr const char *subopts[] = {
+ 				"pad",
++				"stream",
+ 				"fps",
+ 				nullptr
+ 			};
+@@ -320,6 +431,9 @@ void subdev_cmd(int ch, char *optarg)
+ 				set_fps_pad = strtoul(value, nullptr, 0);
+ 				break;
+ 			case 1:
++				set_fps_stream = strtoul(value, nullptr, 0);
++				break;
++			case 2:
+ 				set_fps = strtod(value, nullptr);
+ 				break;
+ 			default:
+@@ -329,6 +443,47 @@ void subdev_cmd(int ch, char *optarg)
+ 			}
+ 		}
+ 		break;
++	case OptSetRouting: {
++		struct v4l2_subdev_route *r;
++		char *end, *ref, *tok;
++		unsigned int flags;
++
++		memset(&routing, 0, sizeof(routing));
++		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
++		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
++		routing.num_routes = 0;
++		routing.routes = (__u64)routes;
++
++		if (!optarg)
++			break;
++
++		r = (v4l2_subdev_route *)routing.routes;
++		ref = end = strdup(optarg);
++		while ((tok = strsep(&end, ",")) != NULL) {
++			if (sscanf(tok, "%u/%u -> %u/%u [%u]",
++				   &r->sink_pad, &r->sink_stream,
++				   &r->source_pad, &r->source_stream,
++				   &flags) != 5) {
++				free(ref);
++				fprintf(stderr, "Invalid route information specified\n");
++				subdev_usage();
++				std::exit(EXIT_FAILURE);
++			}
++
++			if (flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
++				fprintf(stderr, "Invalid route flags specified: %#x\n", flags);
++				subdev_usage();
++				std::exit(EXIT_FAILURE);
++			}
++
++			r->flags = flags;
++
++			r++;
++			routing.num_routes++;
++		}
++		free(ref);
++		break;
++	}
+ 	default:
+ 		break;
+ 	}
+@@ -394,6 +549,7 @@ void subdev_set(cv4l_fd &_fd)
+ 
+ 		memset(&fmt, 0, sizeof(fmt));
+ 		fmt.pad = set_fmt_pad;
++		fmt.stream = set_fmt_stream;
+ 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ 
+ 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0) {
+@@ -430,7 +586,7 @@ void subdev_set(cv4l_fd &_fd)
+ 			else
+ 				fmt.which = V4L2_SUBDEV_FORMAT_TRY;
+ 
+-			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u)\n", fmt.pad);
++			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u,stream=%u)\n", fmt.pad, fmt.stream);
+ 			ret = doioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt);
+ 			if (ret == 0 && (verbose || !options[OptSetSubDevFormat]))
+ 				print_framefmt(fmt.format);
+@@ -441,6 +597,7 @@ void subdev_set(cv4l_fd &_fd)
+ 
+ 		memset(&sel, 0, sizeof(sel));
+ 		sel.pad = vsel.pad;
++		sel.stream = vsel.stream;
+ 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ 		sel.target = vsel.target;
+ 
+@@ -461,7 +618,7 @@ void subdev_set(cv4l_fd &_fd)
+ 			else
+ 				sel.which = V4L2_SUBDEV_FORMAT_TRY;
+ 
+-			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u)\n", sel.pad);
++			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
+ 			int ret = doioctl(fd, VIDIOC_SUBDEV_S_SELECTION, &sel);
+ 			if (ret == 0 && (verbose || !options[OptSetSubDevSelection]))
+ 				print_subdev_selection(sel);
+@@ -472,6 +629,7 @@ void subdev_set(cv4l_fd &_fd)
+ 
+ 		memset(&fival, 0, sizeof(fival));
+ 		fival.pad = set_fps_pad;
++		fival.stream = set_fps_stream;
+ 
+ 		if (set_fps <= 0) {
+ 			fprintf(stderr, "invalid fps %f\n", set_fps);
+@@ -482,7 +640,7 @@ void subdev_set(cv4l_fd &_fd)
+ 		fival.interval.denominator = static_cast<uint32_t>(set_fps * fival.interval.numerator);
+ 		printf("Note: --set-subdev-fps is only for testing.\n"
+ 		       "Normally media-ctl is used to configure the video pipeline.\n");
+-		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u)\n", fival.pad);
++		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
+ 		if (doioctl(fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) == 0) {
+ 			if (!fival.interval.denominator || !fival.interval.numerator)
+ 				printf("\tFrames per second: invalid (%d/%d)\n",
+@@ -493,6 +651,55 @@ void subdev_set(cv4l_fd &_fd)
+ 					fival.interval.denominator, fival.interval.numerator);
+ 		}
+ 	}
++	if (options[OptSetRouting]) {
++		if (doioctl(fd, VIDIOC_SUBDEV_S_ROUTING, &routing) == 0)
++			printf("Routing set\n");
++	}
++}
++
++struct flag_name {
++	__u32 flag;
++	const char *name;
++};
++
++static void print_flags(const struct flag_name *flag_names, unsigned int num_entries, __u32 flags)
++{
++	bool first = true;
++	unsigned int i;
++
++	for (i = 0; i < num_entries; i++) {
++		if (!(flags & flag_names[i].flag))
++			continue;
++		if (!first)
++			printf(",");
++		printf("%s", flag_names[i].name);
++		flags &= ~flag_names[i].flag;
++		first = false;
++	}
++
++	if (flags) {
++		if (!first)
++			printf(",");
++		printf("0x%x", flags);
++	}
++}
++
++static void print_routes(const struct v4l2_subdev_routing *r)
++{
++	unsigned int i;
++	struct v4l2_subdev_route *routes = (struct v4l2_subdev_route *)r->routes;
++
++	static const struct flag_name route_flags[] = {
++		{ V4L2_SUBDEV_ROUTE_FL_ACTIVE, "ACTIVE" },
++	};
++
++	for (i = 0; i < r->num_routes; i++) {
++		printf("%d/%d -> %d/%d [",
++		       routes[i].sink_pad, routes[i].sink_stream,
++		       routes[i].source_pad, routes[i].source_stream);
++		print_flags(route_flags, ARRAY_SIZE(route_flags), routes[i].flags);
++		printf("]\n");
++	}
+ }
+ 
+ void subdev_get(cv4l_fd &_fd)
+@@ -505,8 +712,9 @@ void subdev_get(cv4l_fd &_fd)
+ 		memset(&fmt, 0, sizeof(fmt));
+ 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ 		fmt.pad = get_fmt_pad;
++		fmt.stream = get_fmt_stream;
+ 
+-		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u)\n", fmt.pad);
++		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u, stream=%u)\n", fmt.pad, fmt.stream);
+ 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0)
+ 			print_framefmt(fmt.format);
+ 	}
+@@ -518,8 +726,9 @@ void subdev_get(cv4l_fd &_fd)
+ 		memset(&sel, 0, sizeof(sel));
+ 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ 		sel.pad = get_sel_pad;
++		sel.stream = get_sel_stream;
+ 
+-		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u)\n", sel.pad);
++		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
+ 		if (options[OptAll] || get_sel_target == -1) {
+ 			while (valid_seltarget_at_idx(idx)) {
+ 				sel.target = seltarget_at_idx(idx);
+@@ -538,8 +747,9 @@ void subdev_get(cv4l_fd &_fd)
+ 
+ 		memset(&fival, 0, sizeof(fival));
+ 		fival.pad = get_fps_pad;
++		fival.stream = get_fps_stream;
+ 
+-		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u)\n", fival.pad);
++		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
+ 		if (doioctl(fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) == 0) {
+ 			if (!fival.interval.denominator || !fival.interval.numerator)
+ 				printf("\tFrames per second: invalid (%d/%d)\n",
+@@ -550,6 +760,17 @@ void subdev_get(cv4l_fd &_fd)
+ 					fival.interval.denominator, fival.interval.numerator);
+ 		}
+ 	}
++
++	if (options[OptGetRouting]) {
++		memset(&routing, 0, sizeof(routing));
++		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
++		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
++		routing.num_routes = NUM_ROUTES_MAX;
++		routing.routes = (__u64)routes;
++
++		if (doioctl(fd, VIDIOC_SUBDEV_G_ROUTING, &routing) == 0)
++			print_routes(&routing);
++	}
+ }
+ 
+ static void print_mbus_code(__u32 code)
+@@ -566,11 +787,12 @@ static void print_mbus_code(__u32 code)
+ 		printf("\t0x%04x", code);
+ }
+ 
+-static void print_mbus_codes(int fd, __u32 pad)
++static void print_mbus_codes(int fd, __u32 pad, __u32 stream)
+ {
+ 	struct v4l2_subdev_mbus_code_enum mbus_code = {};
+ 
+ 	mbus_code.pad = pad;
++	mbus_code.stream = stream;
+ 	mbus_code.which = V4L2_SUBDEV_FORMAT_TRY;
+ 
+ 	for (;;) {
+@@ -623,13 +845,13 @@ void subdev_list(cv4l_fd &_fd)
+ 	int fd = _fd.g_fd();
+ 
+ 	if (options[OptListSubDevMBusCodes]) {
+-		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u)\n",
+-		       list_mbus_codes_pad);
+-		print_mbus_codes(fd, list_mbus_codes_pad);
++		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u,stream=%u)\n",
++		       list_mbus_codes_pad, list_mbus_codes_stream);
++		print_mbus_codes(fd, list_mbus_codes_pad, list_mbus_codes_stream);
+ 	}
+ 	if (options[OptListSubDevFrameSizes]) {
+-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u)\n",
+-		       frmsize.pad);
++		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u,stream=%u)\n",
++		       frmsize.pad, frmsize.stream);
+ 		frmsize.index = 0;
+ 		frmsize.which = V4L2_SUBDEV_FORMAT_TRY;
+ 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &frmsize) >= 0) {
+@@ -638,8 +860,8 @@ void subdev_list(cv4l_fd &_fd)
+ 		}
+ 	}
+ 	if (options[OptListSubDevFrameIntervals]) {
+-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u)\n",
+-		       frmival.pad);
++		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u,stream=%u)\n",
++		       frmival.pad, frmival.stream);
+ 		frmival.index = 0;
+ 		frmival.which = V4L2_SUBDEV_FORMAT_TRY;
+ 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &frmival) >= 0) {
+diff --git a/utils/v4l2-ctl/v4l2-ctl.cpp b/utils/v4l2-ctl/v4l2-ctl.cpp
+index 8585278f..1cfb50f7 100644
+--- a/utils/v4l2-ctl/v4l2-ctl.cpp
++++ b/utils/v4l2-ctl/v4l2-ctl.cpp
+@@ -64,6 +64,8 @@ static struct option long_options[] = {
+ 	{"get-fmt-video-out", no_argument, nullptr, OptGetVideoOutFormat},
+ 	{"set-fmt-video-out", required_argument, nullptr, OptSetVideoOutFormat},
+ 	{"try-fmt-video-out", required_argument, nullptr, OptTryVideoOutFormat},
++	{"set-routing", required_argument, 0, OptSetRouting},
++	{"get-routing", no_argument, 0, OptGetRouting},
+ 	{"help", no_argument, nullptr, OptHelp},
+ 	{"help-tuner", no_argument, nullptr, OptHelpTuner},
+ 	{"help-io", no_argument, nullptr, OptHelpIO},
+diff --git a/utils/v4l2-ctl/v4l2-ctl.h b/utils/v4l2-ctl/v4l2-ctl.h
+index 70a80ade..51a68b92 100644
+--- a/utils/v4l2-ctl/v4l2-ctl.h
++++ b/utils/v4l2-ctl/v4l2-ctl.h
+@@ -197,6 +197,8 @@ enum Option {
+ 	OptInfoEdid,
+ 	OptShowEdid,
+ 	OptFixEdidChecksums,
++	OptSetRouting,
++	OptGetRouting,
+ 	OptFreqSeek,
+ 	OptEncoderCmd,
+ 	OptTryEncoderCmd,
+-- 
+2.40.0
+
diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
new file mode 100644
index 00000000..b2d4eded
--- /dev/null
+++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0002-media-ctl-add-support-for-routes-and-streams.patch
@@ -0,0 +1,1022 @@ 
+From 868c176e0de433777d5eed3e6d6d8dc03b9145a6 Mon Sep 17 00:00:00 2001
+From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+Date: Fri, 10 Feb 2023 13:55:45 +0200
+Subject: [PATCH 2/3] media-ctl: add support for routes and streams
+
+Add support to get and set subdev routes and to get and set
+configurations per stream.
+
+Based on work from Sakari Ailus <sakari.ailus@linux.intel.com>.
+
+Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+---
+ utils/media-ctl/libmediactl.c   |  41 +++++
+ utils/media-ctl/libv4l2subdev.c | 283 ++++++++++++++++++++++++++++----
+ utils/media-ctl/media-ctl.c     | 121 ++++++++++++--
+ utils/media-ctl/mediactl.h      |  16 ++
+ utils/media-ctl/options.c       |  15 +-
+ utils/media-ctl/options.h       |   1 +
+ utils/media-ctl/v4l2subdev.h    |  58 ++++++-
+ 7 files changed, 478 insertions(+), 57 deletions(-)
+
+diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
+index 1fd6525b..537365d0 100644
+--- a/utils/media-ctl/libmediactl.c
++++ b/utils/media-ctl/libmediactl.c
+@@ -876,6 +876,47 @@ struct media_pad *media_parse_pad(struct media_device *media,
+ 	return &entity->pads[pad];
+ }
+ 
++struct media_pad *media_parse_pad_stream(struct media_device *media,
++					 const char *p, unsigned int *stream,
++					 char **endp)
++{
++	struct media_pad *pad;
++	const char *orig_p = p;
++	char *ep;
++
++	pad = media_parse_pad(media, p, &ep);
++	if (pad == NULL)
++		return NULL;
++
++	p = ep;
++
++	if (*p == '/') {
++		unsigned int s;
++
++		p++;
++
++		s = strtoul(p, &ep, 10);
++
++		if (ep == p) {
++			printf("Unable to parse stream: '%s'\n", orig_p);
++			if (endp)
++				*endp = (char*)p;
++			return NULL;
++		}
++
++		*stream = s;
++
++		p++;
++	} else {
++		*stream = 0;
++	}
++
++	if (endp)
++		*endp = (char*)p;
++
++	return pad;
++}
++
+ struct media_link *media_parse_link(struct media_device *media,
+ 				    const char *p, char **endp)
+ {
+diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
+index 63bb3d75..d203e5b4 100644
+--- a/utils/media-ctl/libv4l2subdev.c
++++ b/utils/media-ctl/libv4l2subdev.c
+@@ -64,7 +64,7 @@ void v4l2_subdev_close(struct media_entity *entity)
+ }
+ 
+ int v4l2_subdev_get_format(struct media_entity *entity,
+-	struct v4l2_mbus_framefmt *format, unsigned int pad,
++	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
+ 	enum v4l2_subdev_format_whence which)
+ {
+ 	struct v4l2_subdev_format fmt;
+@@ -76,6 +76,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
+ 
+ 	memset(&fmt, 0, sizeof(fmt));
+ 	fmt.pad = pad;
++	fmt.stream = stream;
+ 	fmt.which = which;
+ 
+ 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FMT, &fmt);
+@@ -88,6 +89,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
+ 
+ int v4l2_subdev_set_format(struct media_entity *entity,
+ 	struct v4l2_mbus_framefmt *format, unsigned int pad,
++	unsigned int stream,
+ 	enum v4l2_subdev_format_whence which)
+ {
+ 	struct v4l2_subdev_format fmt;
+@@ -99,6 +101,7 @@ int v4l2_subdev_set_format(struct media_entity *entity,
+ 
+ 	memset(&fmt, 0, sizeof(fmt));
+ 	fmt.pad = pad;
++	fmt.stream = stream;
+ 	fmt.which = which;
+ 	fmt.format = *format;
+ 
+@@ -111,8 +114,8 @@ int v4l2_subdev_set_format(struct media_entity *entity,
+ }
+ 
+ int v4l2_subdev_get_selection(struct media_entity *entity,
+-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
+-	enum v4l2_subdev_format_whence which)
++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
++	unsigned int target, enum v4l2_subdev_format_whence which)
+ {
+ 	union {
+ 		struct v4l2_subdev_selection sel;
+@@ -150,8 +153,8 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
+ }
+ 
+ int v4l2_subdev_set_selection(struct media_entity *entity,
+-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
+-	enum v4l2_subdev_format_whence which)
++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
++	unsigned int target, enum v4l2_subdev_format_whence which)
+ {
+ 	union {
+ 		struct v4l2_subdev_selection sel;
+@@ -165,6 +168,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
+ 
+ 	memset(&u.sel, 0, sizeof(u.sel));
+ 	u.sel.pad = pad;
++	u.sel.stream = stream;
+ 	u.sel.target = target;
+ 	u.sel.which = which;
+ 	u.sel.r = *rect;
+@@ -179,6 +183,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
+ 
+ 	memset(&u.crop, 0, sizeof(u.crop));
+ 	u.crop.pad = pad;
++	u.crop.stream = stream;
+ 	u.crop.which = which;
+ 	u.crop.rect = *rect;
+ 
+@@ -190,6 +195,69 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
+ 	return 0;
+ }
+ 
++int v4l2_subdev_set_routing(struct media_entity *entity,
++			    struct v4l2_subdev_route *routes,
++			    unsigned int num_routes)
++{
++	struct v4l2_subdev_routing routing = {
++		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
++		.routes = (uintptr_t)routes,
++		.num_routes = num_routes,
++	};
++	int ret;
++
++	ret = v4l2_subdev_open(entity);
++	if (ret < 0)
++		return ret;
++
++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
++	if (ret == -1)
++		return -errno;
++
++	return 0;
++}
++
++int v4l2_subdev_get_routing(struct media_entity *entity,
++			    struct v4l2_subdev_route **routes,
++			    unsigned int *num_routes)
++{
++	struct v4l2_subdev_routing routing = { 0 };
++	struct v4l2_subdev_route *r;
++	int ret;
++
++	ret = v4l2_subdev_open(entity);
++	if (ret < 0)
++		return ret;
++
++	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
++
++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
++	if (ret == -1 && errno != ENOSPC)
++		return -errno;
++
++	if (!routing.num_routes) {
++		*routes = NULL;
++		*num_routes = 0;
++		return 0;
++	}
++
++	r = calloc(routing.num_routes, sizeof(*r));
++	if (!r)
++		return -ENOMEM;
++
++	routing.routes = (uintptr_t)r;
++	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
++	if (ret) {
++		free(r);
++		return ret;
++	}
++
++	*num_routes = routing.num_routes;
++	*routes = r;
++
++	return 0;
++}
++
+ int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
+ 	struct v4l2_dv_timings_cap *caps)
+ {
+@@ -264,7 +332,7 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
+ 
+ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
+ 				   struct v4l2_fract *interval,
+-				   unsigned int pad)
++				   unsigned int pad, unsigned int stream)
+ {
+ 	struct v4l2_subdev_frame_interval ival;
+ 	int ret;
+@@ -275,6 +343,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
+ 
+ 	memset(&ival, 0, sizeof(ival));
+ 	ival.pad = pad;
++	ival.stream = stream;
+ 
+ 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &ival);
+ 	if (ret < 0)
+@@ -286,7 +355,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
+ 
+ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
+ 				   struct v4l2_fract *interval,
+-				   unsigned int pad)
++				   unsigned int pad, unsigned int stream)
+ {
+ 	struct v4l2_subdev_frame_interval ival;
+ 	int ret;
+@@ -297,6 +366,7 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
+ 
+ 	memset(&ival, 0, sizeof(ival));
+ 	ival.pad = pad;
++	ival.stream = stream;
+ 	ival.interval = *interval;
+ 
+ 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &ival);
+@@ -307,6 +377,155 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
+ 	return 0;
+ }
+ 
++static int v4l2_subdev_parse_setup_route(struct media_device *media,
++					 struct v4l2_subdev_route *r,
++					 const char *p, char **endp)
++{
++	char *end;
++
++	/* sink pad/stream */
++
++	r->sink_pad = strtoul(p, &end, 10);
++
++	if (*end != '/') {
++		media_dbg(media, "Expected '/'\n");
++		return -EINVAL;
++	}
++
++	p = end + 1;
++
++	r->sink_stream = strtoul(p, &end, 10);
++
++	for (; isspace(*end); ++end);
++
++	if (end[0] != '-' || end[1] != '>') {
++		media_dbg(media, "Expected '->'\n");
++		return -EINVAL;
++	}
++	p = end + 2;
++
++	/* source pad/stream */
++
++	r->source_pad = strtoul(p, &end, 10);
++
++	if (*end != '/') {
++		media_dbg(media, "Expected '/'\n");
++		return -EINVAL;
++	}
++
++	p = end + 1;
++
++	r->source_stream = strtoul(p, &end, 10);
++
++	/* flags */
++
++	for (; isspace(*end); ++end);
++
++	if (*end != '[') {
++		media_dbg(media, "Expected '['\n");
++		return -EINVAL;
++	}
++
++	for (end++; isspace(*end); ++end);
++
++	p = end;
++
++	r->flags = strtoul(p, &end, 0);
++
++	if (r->flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
++		media_dbg(media, "Bad route flags %#x\n", r->flags);
++		return -EINVAL;
++	}
++
++	for (; isspace(*end); ++end);
++
++	if (*end != ']') {
++		media_dbg(media, "Expected ']'\n");
++		return -EINVAL;
++	}
++	end++;
++
++	*endp = end;
++
++	return 0;
++}
++
++int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
++{
++	struct media_entity *entity;
++	struct v4l2_subdev_route *routes;
++	unsigned int num_routes;
++	char *end;
++	int ret;
++	int i;
++
++	entity = media_parse_entity(media, p, &end);
++	if (!entity)
++		return -EINVAL;
++
++	p = end;
++
++	if (*p != '[') {
++		media_dbg(media, "Expected '['\n");
++		return -EINVAL;
++	}
++
++	p++;
++
++	routes = calloc(256, sizeof(routes[0]));
++	if (!routes)
++		return -ENOMEM;
++
++	num_routes = 0;
++
++	while (*p != 0) {
++		struct v4l2_subdev_route *r = &routes[num_routes];
++
++		ret = v4l2_subdev_parse_setup_route(media, r, p, &end);
++		if (ret)
++			goto out;
++
++		p = end;
++
++		num_routes++;
++
++		if (*p == ',') {
++			p++;
++			continue;
++		}
++
++		break;
++	}
++
++	if (*p != ']') {
++		media_dbg(media, "Expected ']'\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	for (i = 0; i < num_routes; ++i) {
++		struct v4l2_subdev_route *r = &routes[i];
++
++		media_dbg(entity->media,
++			  "Setting up route %s : %u/%u -> %u/%u, flags 0x%8.8x\n",
++			  entity->info.name,
++			  r->sink_pad, r->sink_stream,
++			  r->source_pad, r->source_stream,
++			  r->flags);
++	}
++
++	ret = v4l2_subdev_set_routing(entity, routes, num_routes);
++	if (ret) {
++		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
++		goto out;
++	}
++
++out:
++	free(routes);
++
++	return ret;
++}
++
+ static int v4l2_subdev_parse_format(struct media_device *media,
+ 				    struct v4l2_mbus_framefmt *format,
+ 				    const char *p, char **endp)
+@@ -442,7 +661,8 @@ static bool strhazit(const char *str, const char **p)
+ }
+ 
+ static struct media_pad *v4l2_subdev_parse_pad_format(
+-	struct media_device *media, struct v4l2_mbus_framefmt *format,
++	struct media_device *media, unsigned int *stream,
++	struct v4l2_mbus_framefmt *format,
+ 	struct v4l2_rect *crop, struct v4l2_rect *compose,
+ 	struct v4l2_fract *interval, const char *p, char **endp)
+ {
+@@ -453,7 +673,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
+ 
+ 	for (; isspace(*p); ++p);
+ 
+-	pad = media_parse_pad(media, p, &end);
++	pad = media_parse_pad_stream(media, p, stream, &end);
+ 	if (pad == NULL) {
+ 		*endp = end;
+ 		return NULL;
+@@ -675,6 +895,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
+ }
+ 
+ static int set_format(struct media_pad *pad,
++		      unsigned int stream,
+ 		      struct v4l2_mbus_framefmt *format)
+ {
+ 	int ret;
+@@ -683,12 +904,12 @@ static int set_format(struct media_pad *pad,
+ 		return 0;
+ 
+ 	media_dbg(pad->entity->media,
+-		  "Setting up format %s %ux%u on pad %s/%u\n",
++		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
+ 		  v4l2_subdev_pixelcode_to_string(format->code),
+ 		  format->width, format->height,
+-		  pad->entity->info.name, pad->index);
++		  pad->entity->info.name, pad->index, stream);
+ 
+-	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
++	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
+ 				     V4L2_SUBDEV_FORMAT_ACTIVE);
+ 	if (ret < 0) {
+ 		media_dbg(pad->entity->media,
+@@ -705,8 +926,8 @@ static int set_format(struct media_pad *pad,
+ 	return 0;
+ }
+ 
+-static int set_selection(struct media_pad *pad, unsigned int target,
+-			 struct v4l2_rect *rect)
++static int set_selection(struct media_pad *pad, unsigned int stream,
++			 unsigned int target, struct v4l2_rect *rect)
+ {
+ 	int ret;
+ 
+@@ -714,11 +935,11 @@ static int set_selection(struct media_pad *pad, unsigned int target,
+ 		return 0;
+ 
+ 	media_dbg(pad->entity->media,
+-		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
++		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
+ 		  target, rect->left, rect->top, rect->width, rect->height,
+-		  pad->entity->info.name, pad->index);
++		  pad->entity->info.name, pad->index, stream);
+ 
+-	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
++	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
+ 					target, V4L2_SUBDEV_FORMAT_ACTIVE);
+ 	if (ret < 0) {
+ 		media_dbg(pad->entity->media,
+@@ -734,7 +955,7 @@ static int set_selection(struct media_pad *pad, unsigned int target,
+ 	return 0;
+ }
+ 
+-static int set_frame_interval(struct media_pad *pad,
++static int set_frame_interval(struct media_pad *pad, unsigned int stream,
+ 			      struct v4l2_fract *interval)
+ {
+ 	int ret;
+@@ -743,11 +964,12 @@ static int set_frame_interval(struct media_pad *pad,
+ 		return 0;
+ 
+ 	media_dbg(pad->entity->media,
+-		  "Setting up frame interval %u/%u on pad %s/%u\n",
++		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
+ 		  interval->numerator, interval->denominator,
+-		  pad->entity->info.name, pad->index);
++		  pad->entity->info.name, pad->index, stream);
+ 
+-	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
++	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
++					     stream);
+ 	if (ret < 0) {
+ 		media_dbg(pad->entity->media,
+ 			  "Unable to set frame interval: %s (%d)",
+@@ -770,11 +992,13 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
+ 	struct v4l2_rect crop = { -1, -1, -1, -1 };
+ 	struct v4l2_rect compose = crop;
+ 	struct v4l2_fract interval = { 0, 0 };
++	unsigned int stream;
+ 	unsigned int i;
+ 	char *end;
+ 	int ret;
+ 
+-	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
++	pad = v4l2_subdev_parse_pad_format(media, &stream,
++					   &format, &crop, &compose,
+ 					   &interval, p, &end);
+ 	if (pad == NULL) {
+ 		media_print_streampos(media, p, end);
+@@ -783,30 +1007,29 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
+ 	}
+ 
+ 	if (pad->flags & MEDIA_PAD_FL_SINK) {
+-		ret = set_format(pad, &format);
++		ret = set_format(pad, stream, &format);
+ 		if (ret < 0)
+ 			return ret;
+ 	}
+ 
+-	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
++	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
++	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
+ 	if (ret < 0)
+ 		return ret;
+ 
+ 	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
+-		ret = set_format(pad, &format);
++		ret = set_format(pad, stream, &format);
+ 		if (ret < 0)
+ 			return ret;
+ 	}
+ 
+-	ret = set_frame_interval(pad, &interval);
++	ret = set_frame_interval(pad, stream, &interval);
+ 	if (ret < 0)
+ 		return ret;
+ 
+-
+ 	/* If the pad is an output pad, automatically set the same format and
+ 	 * frame interval on the remote subdev input pads, if any.
+ 	 */
+@@ -821,9 +1044,9 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
+ 			if (link->source == pad &&
+ 			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
+ 				remote_format = format;
+-				set_format(link->sink, &remote_format);
++				set_format(link->sink, stream, &remote_format);
+ 
+-				ret = set_frame_interval(link->sink, &interval);
++				ret = set_frame_interval(link->sink, stream, &interval);
+ 				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
+ 					return ret;
+ 			}
+diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
+index 84ee7a83..831136a0 100644
+--- a/utils/media-ctl/media-ctl.c
++++ b/utils/media-ctl/media-ctl.c
+@@ -28,6 +28,7 @@
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <stdbool.h>
++#include <stdint.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -75,23 +76,43 @@ static void print_flags(const struct flag_name *flag_names, unsigned int num_ent
+ 	}
+ }
+ 
++static void v4l2_subdev_print_routes(struct media_entity *entity,
++				     struct v4l2_subdev_route *routes,
++				     unsigned int num_routes)
++{
++	unsigned int i;
++
++	for (i = 0; i < num_routes; i++) {
++		const struct v4l2_subdev_route *r = &routes[i];
++
++		if (i == 0)
++			printf("\troutes:\n");
++
++		printf("\t\t%u/%u -> %u/%u [%s]\n",
++		       r->sink_pad, r->sink_stream,
++		       r->source_pad, r->source_stream,
++		       r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE ? "ACTIVE" : "INACTIVE");
++	}
++}
++
+ static void v4l2_subdev_print_format(struct media_entity *entity,
+-	unsigned int pad, enum v4l2_subdev_format_whence which)
++	unsigned int pad, unsigned int stream,
++	enum v4l2_subdev_format_whence which)
+ {
+ 	struct v4l2_mbus_framefmt format;
+ 	struct v4l2_fract interval = { 0, 0 };
+ 	struct v4l2_rect rect;
+ 	int ret;
+ 
+-	ret = v4l2_subdev_get_format(entity, &format, pad, which);
++	ret = v4l2_subdev_get_format(entity, &format, pad, stream, which);
+ 	if (ret != 0)
+ 		return;
+ 
+-	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad);
++	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad, stream);
+ 	if (ret != 0 && ret != -ENOTTY && ret != -EINVAL)
+ 		return;
+ 
+-	printf("\t\t[fmt:%s/%ux%u",
++	printf("\t\t[stream:%u fmt:%s/%ux%u", stream,
+ 	       v4l2_subdev_pixelcode_to_string(format.code),
+ 	       format.width, format.height);
+ 
+@@ -118,28 +139,28 @@ static void v4l2_subdev_print_format(struct media_entity *entity,
+ 			       v4l2_subdev_quantization_to_string(format.quantization));
+ 	}
+ 
+-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
+ 					V4L2_SEL_TGT_CROP_BOUNDS,
+ 					which);
+ 	if (ret == 0)
+ 		printf("\n\t\t crop.bounds:(%u,%u)/%ux%u", rect.left, rect.top,
+ 		       rect.width, rect.height);
+ 
+-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
+ 					V4L2_SEL_TGT_CROP,
+ 					which);
+ 	if (ret == 0)
+ 		printf("\n\t\t crop:(%u,%u)/%ux%u", rect.left, rect.top,
+ 		       rect.width, rect.height);
+ 
+-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
+ 					V4L2_SEL_TGT_COMPOSE_BOUNDS,
+ 					which);
+ 	if (ret == 0)
+ 		printf("\n\t\t compose.bounds:(%u,%u)/%ux%u",
+ 		       rect.left, rect.top, rect.width, rect.height);
+ 
+-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
++	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
+ 					V4L2_SEL_TGT_COMPOSE,
+ 					which);
+ 	if (ret == 0)
+@@ -455,16 +476,58 @@ static void media_print_topology_dot(struct media_device *media)
+ }
+ 
+ static void media_print_pad_text(struct media_entity *entity,
+-				 const struct media_pad *pad)
++				 const struct media_pad *pad,
++				 struct v4l2_subdev_route *routes,
++				 unsigned int num_routes)
+ {
++	unsigned int i;
++	uint64_t printed_streams_mask;
++
+ 	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
+ 		return;
+ 
+-	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
+-	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
++	if (!routes) {
++		v4l2_subdev_print_format(entity, pad->index, 0, V4L2_SUBDEV_FORMAT_ACTIVE);
++		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
++
++		if (pad->flags & MEDIA_PAD_FL_SOURCE)
++			v4l2_subdev_print_subdev_dv(entity);
++
++		return;
++	}
++
++	printed_streams_mask = 0;
++
++	for (i = 0; i < num_routes; ++i) {
++		const struct v4l2_subdev_route *r = &routes[i];
++		unsigned int stream;
+ 
+-	if (pad->flags & MEDIA_PAD_FL_SOURCE)
+-		v4l2_subdev_print_subdev_dv(entity);
++		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
++			continue;
++
++		if (pad->flags & MEDIA_PAD_FL_SINK) {
++			if (r->sink_pad != pad->index)
++				continue;
++
++			stream = r->sink_stream;
++		} else {
++			if (r->source_pad != pad->index)
++				continue;
++
++			stream = r->source_stream;
++		}
++
++		if (printed_streams_mask & (1 << stream))
++			continue;
++
++		v4l2_subdev_print_format(entity, pad->index, stream, V4L2_SUBDEV_FORMAT_ACTIVE);
++		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
++
++		if (pad->flags & MEDIA_PAD_FL_SOURCE)
++			v4l2_subdev_print_subdev_dv(entity);
++
++		printed_streams_mask |= (1 << stream);
++	}
+ }
+ 
+ static void media_print_topology_text_entity(struct media_device *media,
+@@ -480,11 +543,17 @@ static void media_print_topology_text_entity(struct media_device *media,
+ 	unsigned int num_links = media_entity_get_links_count(entity);
+ 	unsigned int j, k;
+ 	unsigned int padding;
++	struct v4l2_subdev_route *routes = NULL;
++	unsigned int num_routes = 0;
++
++	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
++		v4l2_subdev_get_routing(entity, &routes, &num_routes);
+ 
+ 	padding = printf("- entity %u: ", info->id);
+-	printf("%s (%u pad%s, %u link%s)\n", info->name,
++	printf("%s (%u pad%s, %u link%s, %u route%s)\n", info->name,
+ 	       info->pads, info->pads > 1 ? "s" : "",
+-	       num_links, num_links > 1 ? "s" : "");
++	       num_links, num_links > 1 ? "s" : "",
++	       num_routes, num_routes > 1 ? "s" : "");
+ 	printf("%*ctype %s subtype %s flags %x\n", padding, ' ',
+ 	       media_entity_type_to_string(info->type),
+ 	       media_entity_subtype_to_string(info->type),
+@@ -492,12 +561,15 @@ static void media_print_topology_text_entity(struct media_device *media,
+ 	if (devname)
+ 		printf("%*cdevice node name %s\n", padding, ' ', devname);
+ 
++	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
++		v4l2_subdev_print_routes(entity, routes, num_routes);
++
+ 	for (j = 0; j < info->pads; j++) {
+ 		const struct media_pad *pad = media_entity_get_pad(entity, j);
+ 
+ 		printf("\tpad%u: %s\n", j, media_pad_type_to_string(pad->flags));
+ 
+-		media_print_pad_text(entity, pad);
++		media_print_pad_text(entity, pad, routes, num_routes);
+ 
+ 		for (k = 0; k < num_links; k++) {
+ 			const struct media_link *link = media_entity_get_link(entity, k);
+@@ -521,6 +593,8 @@ static void media_print_topology_text_entity(struct media_device *media,
+ 		}
+ 	}
+ 	printf("\n");
++
++	free(routes);
+ }
+ 
+ static void media_print_topology_text(struct media_device *media)
+@@ -594,14 +668,16 @@ int main(int argc, char **argv)
+ 
+ 	if (media_opts.fmt_pad) {
+ 		struct media_pad *pad;
++		unsigned int stream;
++		char *p;
+ 
+-		pad = media_parse_pad(media, media_opts.fmt_pad, NULL);
++		pad = media_parse_pad_stream(media, media_opts.fmt_pad, &stream, &p);
+ 		if (pad == NULL) {
+ 			printf("Pad '%s' not found\n", media_opts.fmt_pad);
+ 			goto out;
+ 		}
+ 
+-		v4l2_subdev_print_format(pad->entity, pad->index,
++		v4l2_subdev_print_format(pad->entity, pad->index, stream,
+ 					 V4L2_SUBDEV_FORMAT_ACTIVE);
+ 	}
+ 
+@@ -685,6 +761,15 @@ int main(int argc, char **argv)
+ 		}
+ 	}
+ 
++	if (media_opts.routes) {
++		ret = v4l2_subdev_parse_setup_routes(media, media_opts.routes);
++		if (ret) {
++			printf("Unable to setup routes: %s (%d)\n",
++			       strerror(-ret), -ret);
++			goto out;
++		}
++	}
++
+ 	if (media_opts.interactive) {
+ 		while (1) {
+ 			char buffer[32];
+diff --git a/utils/media-ctl/mediactl.h b/utils/media-ctl/mediactl.h
+index af360518..c0fc2962 100644
+--- a/utils/media-ctl/mediactl.h
++++ b/utils/media-ctl/mediactl.h
+@@ -394,6 +394,22 @@ struct media_entity *media_parse_entity(struct media_device *media,
+ struct media_pad *media_parse_pad(struct media_device *media,
+ 				  const char *p, char **endp);
+ 
++/**
++ * @brief Parse string to a pad and stream on the media device.
++ * @param media - media device.
++ * @param p - input string
++ * @param stream - pointer to uint where the stream number is stored
++ * @param endp - pointer to string where parsing ended
++ *
++ * Parse NULL terminated string describing a pad and stream and return its struct
++ * media_pad instance and the stream number.
++ *
++ * @return Pointer to struct media_pad on success, NULL on failure.
++ */
++struct media_pad *media_parse_pad_stream(struct media_device *media,
++					 const char *p, unsigned int *stream,
++					 char **endp);
++
+ /**
+  * @brief Parse string to a link on the media device.
+  * @param media - media device.
+diff --git a/utils/media-ctl/options.c b/utils/media-ctl/options.c
+index 6d30d3dc..58ddec3c 100644
+--- a/utils/media-ctl/options.c
++++ b/utils/media-ctl/options.c
+@@ -63,6 +63,7 @@ static void usage(const char *argv0)
+ 	printf("    --get-v4l2 pad	Print the active format on a given pad\n");
+ 	printf("    --get-dv pad        Print detected and current DV timings on a given pad\n");
+ 	printf("    --set-dv pad	Configure DV timings on a given pad\n");
++	printf("-R, --set-routes routes Configure routes on a given subdev entity\n");
+ 	printf("-h, --help		Show verbose help and exit\n");
+ 	printf("-i, --interactive	Modify links interactively\n");
+ 	printf("-l, --links links	Comma-separated list of link descriptors to setup\n");
+@@ -78,7 +79,7 @@ static void usage(const char *argv0)
+ 	printf("Links and formats are defined as\n");
+ 	printf("\tlinks           = link { ',' link } ;\n");
+ 	printf("\tlink            = pad '->' pad '[' flags ']' ;\n");
+-	printf("\tpad             = entity ':' pad-number ;\n");
++	printf("\tpad             = entity ':' pad-number { '/' stream-number } ;\n");
+ 	printf("\tentity          = entity-number | ( '\"' entity-name '\"' ) ;\n");
+ 	printf("\n");
+ 	printf("\tv4l2            = pad '[' v4l2-properties ']' ;\n");
+@@ -95,11 +96,16 @@ static void usage(const char *argv0)
+ 	printf("\trectangle       = '(' left ',' top, ')' '/' size ;\n");
+ 	printf("\tsize            = width 'x' height ;\n");
+ 	printf("\n");
++	printf("\troutes          = entity '[' route { ',' route } ']' ;\n");
++	printf("\troute           = pad-number '/' stream-number '->' pad-number '/' stream-number '[' route-flags ']' ;\n");
++	printf("\n");
+ 	printf("where the fields are\n");
+ 	printf("\tentity-number   Entity numeric identifier\n");
+ 	printf("\tentity-name     Entity name (string) \n");
+ 	printf("\tpad-number      Pad numeric identifier\n");
++	printf("\tstream-number   Stream numeric identifier\n");
+ 	printf("\tflags           Link flags (0: inactive, 1: active)\n");
++	printf("\troute-flags     Route flags (bitmask of route flags: active - 0x1, immutable - 0x2, source - 0x4)\n");
+ 	printf("\tfcc             Format FourCC\n");
+ 	printf("\twidth           Image width in pixels\n");
+ 	printf("\theight          Image height in pixels\n");
+@@ -152,6 +158,7 @@ static struct option opts[] = {
+ 	{"get-v4l2", 1, 0, OPT_GET_FORMAT},
+ 	{"get-dv", 1, 0, OPT_GET_DV},
+ 	{"set-dv", 1, 0, OPT_SET_DV},
++	{"set-routes", 1, 0, 'R'},
+ 	{"help", 0, 0, 'h'},
+ 	{"interactive", 0, 0, 'i'},
+ 	{"links", 1, 0, 'l'},
+@@ -237,7 +244,7 @@ int parse_cmdline(int argc, char **argv)
+ 	}
+ 
+ 	/* parse options */
+-	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:",
++	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:R:",
+ 				  opts, NULL)) != -1) {
+ 		switch (opt) {
+ 		case 'd':
+@@ -283,6 +290,10 @@ int parse_cmdline(int argc, char **argv)
+ 			media_opts.verbose = 1;
+ 			break;
+ 
++		case 'R':
++			media_opts.routes = optarg;
++			break;
++
+ 		case OPT_PRINT_DOT:
+ 			media_opts.print_dot = 1;
+ 			break;
+diff --git a/utils/media-ctl/options.h b/utils/media-ctl/options.h
+index b1751f56..8796f1b6 100644
+--- a/utils/media-ctl/options.h
++++ b/utils/media-ctl/options.h
+@@ -38,6 +38,7 @@ struct media_options
+ 	const char *fmt_pad;
+ 	const char *get_dv_pad;
+ 	const char *dv_pad;
++	const char *routes;
+ };
+ 
+ extern struct media_options media_opts;
+diff --git a/utils/media-ctl/v4l2subdev.h b/utils/media-ctl/v4l2subdev.h
+index a1813911..a8a6e7ad 100644
+--- a/utils/media-ctl/v4l2subdev.h
++++ b/utils/media-ctl/v4l2subdev.h
+@@ -64,7 +64,7 @@ void v4l2_subdev_close(struct media_entity *entity);
+  * @return 0 on success, or a negative error code on failure.
+  */
+ int v4l2_subdev_get_format(struct media_entity *entity,
+-	struct v4l2_mbus_framefmt *format, unsigned int pad,
++	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
+ 	enum v4l2_subdev_format_whence which);
+ 
+ /**
+@@ -86,6 +86,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
+  */
+ int v4l2_subdev_set_format(struct media_entity *entity,
+ 	struct v4l2_mbus_framefmt *format, unsigned int pad,
++	unsigned int stream,
+ 	enum v4l2_subdev_format_whence which);
+ 
+ /**
+@@ -107,8 +108,8 @@ int v4l2_subdev_set_format(struct media_entity *entity,
+  * @return 0 on success, or a negative error code on failure.
+  */
+ int v4l2_subdev_get_selection(struct media_entity *entity,
+-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
+-	enum v4l2_subdev_format_whence which);
++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
++	unsigned int target, enum v4l2_subdev_format_whence which);
+ 
+ /**
+  * @brief Set a selection rectangle on a pad.
+@@ -129,8 +130,40 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
+  * @return 0 on success, or a negative error code on failure.
+  */
+ int v4l2_subdev_set_selection(struct media_entity *entity,
+-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
+-	enum v4l2_subdev_format_whence which);
++	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
++	unsigned int target, enum v4l2_subdev_format_whence which);
++
++/**
++ * @brief Get the routing table of a subdev media entity.
++ * @param entity - subdev-device media entity.
++ * @param routes - routes of the subdev.
++ * @param num_routes - number of routes.
++ *
++ * Get the routes of @a entity and return them in an allocated array in @a routes
++ * and the number of routes in @a num_routes.
++ *
++ * The caller is responsible for freeing the routes array after use.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_get_routing(struct media_entity *entity,
++			    struct v4l2_subdev_route **routes,
++			    unsigned int *num_routes);
++
++/**
++ * @brief Set the routing table of a subdev media entity.
++ * @param entity - subdev-device media entity.
++ * @param routes - routes of the subdev.
++ * @param num_routes - number of routes.
++ *
++ * Set the routes of @a entity. The routes are given in @a routes with the
++ * length of @a num_routes.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_set_routing(struct media_entity *entity,
++			    struct v4l2_subdev_route *route,
++			    unsigned int num_routes);
+ 
+ /**
+  * @brief Query the digital video capabilities of a pad.
+@@ -200,7 +233,7 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
+  */
+ 
+ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
+-	struct v4l2_fract *interval, unsigned int pad);
++	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
+ 
+ /**
+  * @brief Set the frame interval on a sub-device.
+@@ -217,7 +250,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
+  * @return 0 on success, or a negative error code on failure.
+  */
+ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
+-	struct v4l2_fract *interval, unsigned int pad);
++	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
+ 
+ /**
+  * @brief Parse a string and apply format, crop and frame interval settings.
+@@ -235,6 +268,17 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
+  */
+ int v4l2_subdev_parse_setup_formats(struct media_device *media, const char *p);
+ 
++/**
++ * @brief Parse a string and apply route settings.
++ * @param media - media device.
++ * @param p - input string
++ *
++ * Parse string @a p and apply route settings to a subdev.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p);
++
+ /**
+  * @brief Convert media bus pixel code to string.
+  * @param code - input string
+-- 
+2.40.0
+
diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch
new file mode 100644
index 00000000..32eeb02a
--- /dev/null
+++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils/0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch
@@ -0,0 +1,460 @@ 
+From 2866c81d2597f47ed976928bc9c27942bbf095f0 Mon Sep 17 00:00:00 2001
+From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+Date: Fri, 10 Feb 2023 13:55:46 +0200
+Subject: [PATCH 3/3] v4l2-ctl/compliance: add routing and streams multiplexed
+ streams
+
+Add basic support for routing and streams.
+
+Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+---
+ utils/v4l2-compliance/v4l2-compliance.cpp   | 120 ++++++++++++++++----
+ utils/v4l2-compliance/v4l2-compliance.h     |   8 +-
+ utils/v4l2-compliance/v4l2-test-subdevs.cpp |  43 ++++++-
+ 3 files changed, 137 insertions(+), 34 deletions(-)
+
+diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
+index 8aebae2e..63b5fbbb 100644
+--- a/utils/v4l2-compliance/v4l2-compliance.cpp
++++ b/utils/v4l2-compliance/v4l2-compliance.cpp
+@@ -1224,6 +1224,10 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
+ 	if (node.is_subdev()) {
+ 		bool has_source = false;
+ 		bool has_sink = false;
++		struct v4l2_subdev_routing sd_routing[2] = {};
++		struct v4l2_subdev_route sd_routes[2][256] = {};
++		bool has_routes = !!(subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
++		int ret;
+ 
+ 		node.frame_interval_pad = -1;
+ 		node.enum_frame_interval_pad = -1;
+@@ -1235,6 +1239,22 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
+ 		}
+ 		node.is_passthrough_subdev = has_source && has_sink;
+ 
++		if (has_routes) {
++			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
++				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
++
++				sd_routing[which].which = which;
++				sd_routing[which].routes = (__u64)sd_routes[which];
++				sd_routing[which].num_routes = 256;
++
++				ret = doioctl(&node, VIDIOC_SUBDEV_G_ROUTING, &sd_routing[which]);
++				if (ret) {
++					fail("VIDIOC_SUBDEV_G_ROUTING: failed to get routing\n");
++					sd_routing[which].num_routes = 0;
++				}
++			}
++		}
++
+ 		for (unsigned pad = 0; pad < node.entity.pads; pad++) {
+ 			printf("Sub-Device ioctls (%s Pad %u):\n",
+ 			       (node.pads[pad].flags & MEDIA_PAD_FL_SINK) ?
+@@ -1244,32 +1264,82 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
+ 			node.has_subdev_enum_fival = 0;
+ 			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
+ 			     which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
+-				printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
+-				       which ? "Active" : "Try",
+-				       ok(testSubDevEnum(&node, which, pad)));
+-				printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
+-				       which ? "Active" : "Try",
+-				       ok(testSubDevFormat(&node, which, pad)));
+-				printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
+-				       which ? "Active" : "Try",
+-				       ok(testSubDevSelection(&node, which, pad)));
+-				if (which)
+-					printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
+-					       ok(testSubDevFrameInterval(&node, pad)));
++				struct v4l2_subdev_routing dummy_routing;
++				struct v4l2_subdev_route dummy_routes[1];
++
++				const struct v4l2_subdev_routing *routing;
++				const struct v4l2_subdev_route *routes;
++
++				if (has_routes) {
++					routing = &sd_routing[which];
++					routes = sd_routes[which];
++				} else {
++					dummy_routing.num_routes = 1;
++					dummy_routing.routes = (__u64)&dummy_routes;
++					dummy_routes[0].source_pad = pad;
++					dummy_routes[0].source_stream = 0;
++					dummy_routes[0].sink_pad = pad;
++					dummy_routes[0].sink_stream = 0;
++					dummy_routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
++
++					routing = &dummy_routing;
++					routes = dummy_routes;
++				}
++
++				for (unsigned i = 0; i < routing->num_routes; ++i) {
++					const struct v4l2_subdev_route *r = &routes[i];
++					unsigned stream;
++
++					if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
++						continue;
++
++					if ((node.pads[pad].flags & MEDIA_PAD_FL_SINK) &&
++					    (r->sink_pad == pad))
++						stream = r->sink_stream;
++					else if ((node.pads[pad].flags & MEDIA_PAD_FL_SOURCE) &&
++					    (r->source_pad == pad))
++						stream = r->source_stream;
++					else
++						continue;
++
++					printf("\t%s Stream %u\n",which ? "Active" : "Try",
++					       stream);
++
++					printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
++					       which ? "Active" : "Try",
++					       ok(testSubDevEnum(&node, which, pad, stream)));
++					printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
++					       which ? "Active" : "Try",
++					       ok(testSubDevFormat(&node, which, pad, stream)));
++					printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
++					       which ? "Active" : "Try",
++					       ok(testSubDevSelection(&node, which, pad, stream)));
++					if (which)
++						printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
++						       ok(testSubDevFrameInterval(&node, pad, stream)));
++				}
++			}
++
++			/*
++			 * These tests do not make sense for subdevs with multiplexed streams,
++			 * as the try & active cases may have different routing and thus different
++			 * behavior.
++			 */
++			if (!has_routes) {
++				if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
++					fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
++				if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
++					fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
++				if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
++					fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
++				if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
++					fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
++				if (node.has_subdev_selection && node.has_subdev_selection < 3)
++					fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
++				if (node.has_subdev_selection &&
++				    node.has_subdev_selection != node.has_subdev_fmt)
++					fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
+ 			}
+-			if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
+-				fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
+-			if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
+-				fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
+-			if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
+-				fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
+-			if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
+-				fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
+-			if (node.has_subdev_selection && node.has_subdev_selection < 3)
+-				fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
+-			if (node.has_subdev_selection &&
+-			    node.has_subdev_selection != node.has_subdev_fmt)
+-				fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
+ 			printf("\n");
+ 		}
+ 	}
+diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
+index e574c06c..67b3521e 100644
+--- a/utils/v4l2-compliance/v4l2-compliance.h
++++ b/utils/v4l2-compliance/v4l2-compliance.h
+@@ -373,10 +373,10 @@ int testDecoder(struct node *node);
+ 
+ // SubDev ioctl tests
+ int testSubDevCap(struct node *node);
+-int testSubDevEnum(struct node *node, unsigned which, unsigned pad);
+-int testSubDevFormat(struct node *node, unsigned which, unsigned pad);
+-int testSubDevSelection(struct node *node, unsigned which, unsigned pad);
+-int testSubDevFrameInterval(struct node *node, unsigned pad);
++int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream);
++int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
++int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
++int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
+ 
+ // Buffer ioctl tests
+ int testReqBufs(struct node *node);
+diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
+index f3d85771..07192bda 100644
+--- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
++++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
+@@ -25,7 +25,7 @@
+ 
+ #include "v4l2-compliance.h"
+ 
+-#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV)
++#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV | V4L2_SUBDEV_CAP_STREAMS)
+ 
+ int testSubDevCap(struct node *node)
+ {
+@@ -54,6 +54,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
+ 	memset(&fie, 0, sizeof(fie));
+ 	fie.which = which;
+ 	fie.pad = pad;
++	fie.stream = 0;
+ 	fie.code = code;
+ 	fie.width = width;
+ 	fie.height = height;
+@@ -83,6 +84,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
+ 	memset(&fie, 0xff, sizeof(fie));
+ 	fie.which = which;
+ 	fie.pad = pad;
++	fie.stream = 0;
+ 	fie.code = code;
+ 	fie.width = width;
+ 	fie.height = height;
+@@ -128,6 +130,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
+ 	memset(&fse, 0, sizeof(fse));
+ 	fse.which = which;
+ 	fse.pad = pad;
++	fse.stream = 0;
+ 	fse.code = code;
+ 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse);
+ 	node->has_subdev_enum_fsize |= (ret != ENOTTY) << which;
+@@ -137,6 +140,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
+ 		memset(&fie, 0, sizeof(fie));
+ 		fie.which = which;
+ 		fie.pad = pad;
++		fie.stream = 0;
+ 		fie.code = code;
+ 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
+ 		return ret;
+@@ -152,6 +156,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
+ 	memset(&fse, 0xff, sizeof(fse));
+ 	fse.which = which;
+ 	fse.pad = pad;
++	fse.stream = 0;
+ 	fse.code = code;
+ 	fse.index = 0;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse));
+@@ -195,7 +200,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
+ 	return 0;
+ }
+ 
+-int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
++int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream)
+ {
+ 	struct v4l2_subdev_mbus_code_enum mbus_core_enum;
+ 	unsigned num_codes;
+@@ -204,6 +209,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+ 	memset(&mbus_core_enum, 0, sizeof(mbus_core_enum));
+ 	mbus_core_enum.which = which;
+ 	mbus_core_enum.pad = pad;
++	mbus_core_enum.stream = stream;
+ 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum);
+ 	node->has_subdev_enum_code |= (ret != ENOTTY) << which;
+ 	if (ret == ENOTTY) {
+@@ -214,8 +220,10 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+ 		memset(&fie, 0, sizeof(fie));
+ 		fse.which = which;
+ 		fse.pad = pad;
++		fse.stream = stream;
+ 		fie.which = which;
+ 		fie.pad = pad;
++		fie.stream = stream;
+ 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse) != ENOTTY);
+ 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
+ 		return ret;
+@@ -226,16 +234,19 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+ 	mbus_core_enum.index = ~0;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
+ 	mbus_core_enum.pad = node->entity.pads;
++	mbus_core_enum.stream = stream;
+ 	mbus_core_enum.index = 0;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
+ 	memset(&mbus_core_enum, 0xff, sizeof(mbus_core_enum));
+ 	mbus_core_enum.which = which;
+ 	mbus_core_enum.pad = pad;
++	mbus_core_enum.stream = stream;
+ 	mbus_core_enum.index = 0;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum));
+ 	fail_on_test(check_0(mbus_core_enum.reserved, sizeof(mbus_core_enum.reserved)));
+ 	fail_on_test(mbus_core_enum.code == ~0U);
+ 	fail_on_test(mbus_core_enum.pad != pad);
++	fail_on_test(mbus_core_enum.stream != stream);
+ 	fail_on_test(mbus_core_enum.index);
+ 	fail_on_test(mbus_core_enum.which != which);
+ 	do {
+@@ -252,6 +263,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+ 		fail_on_test(!mbus_core_enum.code);
+ 		fail_on_test(mbus_core_enum.which != which);
+ 		fail_on_test(mbus_core_enum.pad != pad);
++		fail_on_test(mbus_core_enum.stream != stream);
+ 		fail_on_test(mbus_core_enum.index != i);
+ 
+ 		ret = testSubDevEnumFrameSize(node, which, pad, mbus_core_enum.code);
+@@ -260,7 +272,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+ 	return 0;
+ }
+ 
+-int testSubDevFrameInterval(struct node *node, unsigned pad)
++int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream)
+ {
+ 	struct v4l2_subdev_frame_interval fival;
+ 	struct v4l2_fract ival;
+@@ -268,6 +280,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
+ 
+ 	memset(&fival, 0xff, sizeof(fival));
+ 	fival.pad = pad;
++	fival.stream = stream;
+ 	ret = doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival);
+ 	if (ret == ENOTTY) {
+ 		fail_on_test(node->enum_frame_interval_pad >= 0);
+@@ -279,6 +292,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
+ 	node->frame_interval_pad = pad;
+ 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
+ 	fail_on_test(fival.pad != pad);
++	fail_on_test(fival.stream != stream);
+ 	fail_on_test(!fival.interval.numerator);
+ 	fail_on_test(!fival.interval.denominator);
+ 	fail_on_test(fival.interval.numerator == ~0U || fival.interval.denominator == ~0U);
+@@ -290,20 +304,25 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
+ 	}
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
+ 	fail_on_test(fival.pad != pad);
++	fail_on_test(fival.stream != stream);
+ 	fail_on_test(ival.numerator != fival.interval.numerator);
+ 	fail_on_test(ival.denominator != fival.interval.denominator);
+ 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
+ 	memset(&fival, 0, sizeof(fival));
+ 	fival.pad = pad;
++	fival.stream = stream;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival));
+ 	fail_on_test(fival.pad != pad);
++	fail_on_test(fival.stream != stream);
+ 	fail_on_test(ival.numerator != fival.interval.numerator);
+ 	fail_on_test(ival.denominator != fival.interval.denominator);
+ 
+ 	fival.pad = node->entity.pads;
++	fival.stream = stream;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) != EINVAL);
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) != EINVAL);
+ 	fival.pad = pad;
++	fival.stream = stream;
+ 	fival.interval = ival;
+ 	fival.interval.numerator = 0;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
+@@ -340,7 +359,7 @@ static int checkMBusFrameFmt(struct node *node, struct v4l2_mbus_framefmt &fmt)
+ 	return 0;
+ }
+ 
+-int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
++int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream)
+ {
+ 	struct v4l2_subdev_format fmt;
+ 	struct v4l2_subdev_format s_fmt;
+@@ -349,6 +368,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
+ 	memset(&fmt, 0, sizeof(fmt));
+ 	fmt.which = which;
+ 	fmt.pad = pad;
++	fmt.stream = stream;
+ 	ret = doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt);
+ 	node->has_subdev_fmt |= (ret != ENOTTY) << which;
+ 	if (ret == ENOTTY) {
+@@ -359,14 +379,17 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
+ 	fmt.which = 0;
+ 	fmt.pad = node->entity.pads;
++	fmt.stream = stream;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
+ 	memset(&fmt, 0xff, sizeof(fmt));
+ 	fmt.which = which;
+ 	fmt.pad = pad;
++	fmt.stream = stream;
+ 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt));
+ 	fail_on_test(check_0(fmt.reserved, sizeof(fmt.reserved)));
+ 	fail_on_test(fmt.which != which);
+ 	fail_on_test(fmt.pad != pad);
++	fail_on_test(fmt.stream != stream);
+ 	fail_on_test(checkMBusFrameFmt(node, fmt.format));
+ 	s_fmt = fmt;
+ 	memset(s_fmt.reserved, 0xff, sizeof(s_fmt.reserved));
+@@ -379,6 +402,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
+ 	fail_on_test(ret && ret != ENOTTY);
+ 	fail_on_test(s_fmt.which != which);
+ 	fail_on_test(s_fmt.pad != pad);
++	fail_on_test(s_fmt.stream != stream);
+ 	if (ret) {
+ 		warn("VIDIOC_SUBDEV_G_FMT is supported but not VIDIOC_SUBDEV_S_FMT\n");
+ 		return 0;
+@@ -423,7 +447,7 @@ static target_info targets[] = {
+ 	{ ~0U },
+ };
+ 
+-int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
++int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream)
+ {
+ 	struct v4l2_subdev_selection sel;
+ 	struct v4l2_subdev_selection s_sel;
+@@ -435,10 +459,12 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 	targets[V4L2_SEL_TGT_NATIVE_SIZE].readonly = is_sink;
+ 	memset(&crop, 0, sizeof(crop));
+ 	crop.pad = pad;
++	crop.stream = stream;
+ 	crop.which = which;
+ 	memset(&sel, 0, sizeof(sel));
+ 	sel.which = which;
+ 	sel.pad = pad;
++	sel.stream = stream;
+ 	sel.target = V4L2_SEL_TGT_CROP;
+ 	ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
+ 	node->has_subdev_selection |= (ret != ENOTTY) << which;
+@@ -451,6 +477,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 	fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
+ 	fail_on_test(crop.which != which);
+ 	fail_on_test(crop.pad != pad);
++	fail_on_test(crop.stream != stream);
+ 	fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
+ 
+ 	for (unsigned tgt = 0; targets[tgt].target != ~0U; tgt++) {
+@@ -458,6 +485,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 		memset(&sel, 0xff, sizeof(sel));
+ 		sel.which = which;
+ 		sel.pad = pad;
++		sel.stream = stream;
+ 		sel.target = tgt;
+ 		ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
+ 		targets[tgt].found = !ret;
+@@ -469,6 +497,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 		fail_on_test(check_0(sel.reserved, sizeof(sel.reserved)));
+ 		fail_on_test(sel.which != which);
+ 		fail_on_test(sel.pad != pad);
++		fail_on_test(sel.stream != stream);
+ 		fail_on_test(sel.target != tgt);
+ 		fail_on_test(!sel.r.width);
+ 		fail_on_test(sel.r.width == ~0U);
+@@ -480,9 +509,11 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
+ 		sel.which = 0;
+ 		sel.pad = node->entity.pads;
++		sel.stream = stream;
+ 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
+ 		sel.which = which;
+ 		sel.pad = pad;
++		sel.stream = stream;
+ 		s_sel = sel;
+ 		memset(s_sel.reserved, 0xff, sizeof(s_sel.reserved));
+ 		ret = doioctl(node, VIDIOC_SUBDEV_S_SELECTION, &s_sel);
+@@ -496,6 +527,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 				fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
+ 				fail_on_test(crop.which != which);
+ 				fail_on_test(crop.pad != pad);
++				fail_on_test(crop.stream != stream);
+ 				fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
+ 			}
+ 		}
+@@ -504,6 +536,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+ 		fail_on_test(!ret && targets[tgt].readonly);
+ 		fail_on_test(s_sel.which != which);
+ 		fail_on_test(s_sel.pad != pad);
++		fail_on_test(s_sel.stream != stream);
+ 		if (ret && !targets[tgt].readonly && tgt != V4L2_SEL_TGT_NATIVE_SIZE)
+ 			warn("VIDIOC_SUBDEV_G_SELECTION is supported for target %u but not VIDIOC_SUBDEV_S_SELECTION\n", tgt);
+ 		if (ret)
+-- 
+2.40.0
+
diff --git a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
index c604ebbc..a2ebb0ea 100644
--- a/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
+++ b/meta-arago-extras/recipes-multimedia/v4l-utils/v4l-utils_1.24.1.bb
@@ -31,6 +31,10 @@  SRC_URI = "\
     file://0002-original-patch-mediactl-pkgconfig.patch \
     file://0003-original-patch-export-mediactl-headers.patch \
     file://0004-Do-not-use-getsubopt.patch \
+    file://0001-v4l2-ctl-Add-routing-and-streams-support.patch \
+    file://0002-media-ctl-add-support-for-routes-and-streams.patch \
+    file://0003-v4l2-ctl-compliance-add-routing-and-streams-multiple.patch \
+    file://0001-media-ctl-add-support-for-RGBIr-bayer-formats.patch \
 "
 
 SRC_URI[md5sum] = "8ba9c73c4319b6afab5fa4358edc43de"